diff --git a/Src/FluentAssertions/FluentAssertions.csproj b/Src/FluentAssertions/FluentAssertions.csproj index 1a3d1f3de8..c12fb3b555 100644 --- a/Src/FluentAssertions/FluentAssertions.csproj +++ b/Src/FluentAssertions/FluentAssertions.csproj @@ -40,6 +40,9 @@ <_Parameter1>FluentAssertions.Specs, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d25ff515c85b13ba08f61d466cff5d80a7f28ba197bbf8796085213e7a3406f970d2a4874932fed35db546e89af2da88c194bf1b7f7ac70de7988c78406f7629c547283061282a825616eb7eb48a9514a7570942936020a9bb37dca9ff60b778309900851575614491c6d25018fadb75828f4c7a17bf2d7dc86e7b6eafc5d8f + + <_Parameter1>FluentAssertions.Equivalency.Specs, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d25ff515c85b13ba08f61d466cff5d80a7f28ba197bbf8796085213e7a3406f970d2a4874932fed35db546e89af2da88c194bf1b7f7ac70de7988c78406f7629c547283061282a825616eb7eb48a9514a7570942936020a9bb37dca9ff60b778309900851575614491c6d25018fadb75828f4c7a17bf2d7dc86e7b6eafc5d8f + <_Parameter1>Benchmarks, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d25ff515c85b13ba08f61d466cff5d80a7f28ba197bbf8796085213e7a3406f970d2a4874932fed35db546e89af2da88c194bf1b7f7ac70de7988c78406f7629c547283061282a825616eb7eb48a9514a7570942936020a9bb37dca9ff60b778309900851575614491c6d25018fadb75828f4c7a17bf2d7dc86e7b6eafc5d8f diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt index adda36a474..b449218afb 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt @@ -1,5 +1,6 @@ [assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/fluentassertions/fluentassertions")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"Benchmarks, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d25ff515c85b13ba08f61d466cff5d80a7f28ba197bbf8796085213e7a3406f970d2a4874932fed35db546e89af2da88c194bf1b7f7ac70de7988c78406f7629c547283061282a825616eb7eb48a9514a7570942936020a9bb37dca9ff60b778309900851575614491c6d25018fadb75828f4c7a17bf2d7dc86e7b6eafc5d8f")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"FluentAssertions.Equivalency.Specs, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d25ff515c85b13ba08f61d466cff5d80a7f28ba197bbf8796085213e7a3406f970d2a4874932fed35db546e89af2da88c194bf1b7f7ac70de7988c78406f7629c547283061282a825616eb7eb48a9514a7570942936020a9bb37dca9ff60b778309900851575614491c6d25018fadb75828f4c7a17bf2d7dc86e7b6eafc5d8f")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"FluentAssertions.Specs, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d25ff515c85b13ba08f61d466cff5d80a7f28ba197bbf8796085213e7a3406f970d2a4874932fed35db546e89af2da88c194bf1b7f7ac70de7988c78406f7629c547283061282a825616eb7eb48a9514a7570942936020a9bb37dca9ff60b778309900851575614491c6d25018fadb75828f4c7a17bf2d7dc86e7b6eafc5d8f")] [assembly: System.Runtime.Versioning.TargetFramework(".NETFramework,Version=v4.7", FrameworkDisplayName=".NET Framework 4.7")] namespace FluentAssertions diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp2.1.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp2.1.verified.txt index 6540a4e08b..d5e90c248e 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp2.1.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp2.1.verified.txt @@ -1,5 +1,6 @@ [assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/fluentassertions/fluentassertions")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"Benchmarks, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d25ff515c85b13ba08f61d466cff5d80a7f28ba197bbf8796085213e7a3406f970d2a4874932fed35db546e89af2da88c194bf1b7f7ac70de7988c78406f7629c547283061282a825616eb7eb48a9514a7570942936020a9bb37dca9ff60b778309900851575614491c6d25018fadb75828f4c7a17bf2d7dc86e7b6eafc5d8f")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"FluentAssertions.Equivalency.Specs, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d25ff515c85b13ba08f61d466cff5d80a7f28ba197bbf8796085213e7a3406f970d2a4874932fed35db546e89af2da88c194bf1b7f7ac70de7988c78406f7629c547283061282a825616eb7eb48a9514a7570942936020a9bb37dca9ff60b778309900851575614491c6d25018fadb75828f4c7a17bf2d7dc86e7b6eafc5d8f")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"FluentAssertions.Specs, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d25ff515c85b13ba08f61d466cff5d80a7f28ba197bbf8796085213e7a3406f970d2a4874932fed35db546e89af2da88c194bf1b7f7ac70de7988c78406f7629c547283061282a825616eb7eb48a9514a7570942936020a9bb37dca9ff60b778309900851575614491c6d25018fadb75828f4c7a17bf2d7dc86e7b6eafc5d8f")] [assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v2.1", FrameworkDisplayName="")] namespace FluentAssertions diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp3.0.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp3.0.verified.txt index d3f72b2cbf..ee25dd44c0 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp3.0.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp3.0.verified.txt @@ -1,5 +1,6 @@ [assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/fluentassertions/fluentassertions")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"Benchmarks, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d25ff515c85b13ba08f61d466cff5d80a7f28ba197bbf8796085213e7a3406f970d2a4874932fed35db546e89af2da88c194bf1b7f7ac70de7988c78406f7629c547283061282a825616eb7eb48a9514a7570942936020a9bb37dca9ff60b778309900851575614491c6d25018fadb75828f4c7a17bf2d7dc86e7b6eafc5d8f")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"FluentAssertions.Equivalency.Specs, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d25ff515c85b13ba08f61d466cff5d80a7f28ba197bbf8796085213e7a3406f970d2a4874932fed35db546e89af2da88c194bf1b7f7ac70de7988c78406f7629c547283061282a825616eb7eb48a9514a7570942936020a9bb37dca9ff60b778309900851575614491c6d25018fadb75828f4c7a17bf2d7dc86e7b6eafc5d8f")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"FluentAssertions.Specs, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d25ff515c85b13ba08f61d466cff5d80a7f28ba197bbf8796085213e7a3406f970d2a4874932fed35db546e89af2da88c194bf1b7f7ac70de7988c78406f7629c547283061282a825616eb7eb48a9514a7570942936020a9bb37dca9ff60b778309900851575614491c6d25018fadb75828f4c7a17bf2d7dc86e7b6eafc5d8f")] [assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v3.0", FrameworkDisplayName="")] namespace FluentAssertions diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt index 49034c7df4..e9d45913dc 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt @@ -1,5 +1,6 @@ [assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/fluentassertions/fluentassertions")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"Benchmarks, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d25ff515c85b13ba08f61d466cff5d80a7f28ba197bbf8796085213e7a3406f970d2a4874932fed35db546e89af2da88c194bf1b7f7ac70de7988c78406f7629c547283061282a825616eb7eb48a9514a7570942936020a9bb37dca9ff60b778309900851575614491c6d25018fadb75828f4c7a17bf2d7dc86e7b6eafc5d8f")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"FluentAssertions.Equivalency.Specs, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d25ff515c85b13ba08f61d466cff5d80a7f28ba197bbf8796085213e7a3406f970d2a4874932fed35db546e89af2da88c194bf1b7f7ac70de7988c78406f7629c547283061282a825616eb7eb48a9514a7570942936020a9bb37dca9ff60b778309900851575614491c6d25018fadb75828f4c7a17bf2d7dc86e7b6eafc5d8f")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"FluentAssertions.Specs, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d25ff515c85b13ba08f61d466cff5d80a7f28ba197bbf8796085213e7a3406f970d2a4874932fed35db546e89af2da88c194bf1b7f7ac70de7988c78406f7629c547283061282a825616eb7eb48a9514a7570942936020a9bb37dca9ff60b778309900851575614491c6d25018fadb75828f4c7a17bf2d7dc86e7b6eafc5d8f")] [assembly: System.Runtime.Versioning.TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName="")] namespace FluentAssertions diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt index 4a79a41215..84a9aa9c4e 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt @@ -1,5 +1,6 @@ [assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/fluentassertions/fluentassertions")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"Benchmarks, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d25ff515c85b13ba08f61d466cff5d80a7f28ba197bbf8796085213e7a3406f970d2a4874932fed35db546e89af2da88c194bf1b7f7ac70de7988c78406f7629c547283061282a825616eb7eb48a9514a7570942936020a9bb37dca9ff60b778309900851575614491c6d25018fadb75828f4c7a17bf2d7dc86e7b6eafc5d8f")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"FluentAssertions.Equivalency.Specs, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d25ff515c85b13ba08f61d466cff5d80a7f28ba197bbf8796085213e7a3406f970d2a4874932fed35db546e89af2da88c194bf1b7f7ac70de7988c78406f7629c547283061282a825616eb7eb48a9514a7570942936020a9bb37dca9ff60b778309900851575614491c6d25018fadb75828f4c7a17bf2d7dc86e7b6eafc5d8f")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo(@"FluentAssertions.Specs, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d25ff515c85b13ba08f61d466cff5d80a7f28ba197bbf8796085213e7a3406f970d2a4874932fed35db546e89af2da88c194bf1b7f7ac70de7988c78406f7629c547283061282a825616eb7eb48a9514a7570942936020a9bb37dca9ff60b778309900851575614491c6d25018fadb75828f4c7a17bf2d7dc86e7b6eafc5d8f")] [assembly: System.Runtime.Versioning.TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName="")] namespace FluentAssertions diff --git a/Tests/FluentAssertions.Equivalency.Specs/.editorconfig b/Tests/FluentAssertions.Equivalency.Specs/.editorconfig new file mode 100644 index 0000000000..436717d4cf --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/.editorconfig @@ -0,0 +1,141 @@ +[*.cs] + +# IDE0051: Private member is unused +dotnet_diagnostic.IDE0051.severity = none +# IDE0070: GetHashCode implementation can be simplified +dotnet_diagnostic.IDE0070.severity = none + +# CA1002 Change 'List' to use 'Collection', 'ReadOnlyCollection' or 'KeyedCollection' +dotnet_diagnostic.CA1002.severity = none +# CA1003 Change the event to replace the type with a generic EventHandler +dotnet_diagnostic.CA1003.severity = none +# CA1008: Add a member to Color that has a value of zero with a suggested name of 'None' +dotnet_diagnostic.CA1008.severity = none +# CA1014 Mark assemblies with CLSCompliant +dotnet_diagnostic.CA1014.severity = none +# CA1017 Mark assembly with ComVisisble(false) +dotnet_diagnostic.CA1017.severity = none +# CA1024 Use properties where appropriate +dotnet_diagnostic.CA1024.severity = none +# CA1028: Enum Storage should be Int32 +dotnet_diagnostic.CA1028.severity = none +# CA1032: Implement standard exception constructors +dotnet_diagnostic.CA1032.severity = none +# CA1036: Override methods on comparable types +dotnet_diagnostic.CA1036.severity = none +# CA1040: Avoid empty interfaces +dotnet_diagnostic.CA1040.severity = none +# CA1044: Properties should not be write only +dotnet_diagnostic.CA1044.severity = none +# CA1051: Do not declare visible instance fields +dotnet_diagnostic.CA1051.severity = none +# CA1062: Validate arguments of public methods +dotnet_diagnostic.CA1062.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 +# CA1707: Remove the underscores from member name +dotnet_diagnostic.CA1707.severity = none +# CA1711: Rename type name so that it does not end in 'Enum' +dotnet_diagnostic.CA1711.severity = none +# CA1714: Flags enums should have plural names +dotnet_diagnostic.CA1714.severity = none +# CA1716: Identifiers should not match keywords +dotnet_diagnostic.CA1716.severity = none +# CA1813 Avoid unsealed attributes +dotnet_diagnostic.CA1813.severity = none +# CA1814: Prefer jagged arrays over multidimensional +dotnet_diagnostic.CA1814.severity = none +# CA1818: Type is an internal class that is apparently never instantiated. +dotnet_diagnostic.CA1812.severity = none +# CA1822: Member does not access instance data and can be marked as static +dotnet_diagnostic.CA1822.severity = none +# CA2000: Dispose objects before losing scope +dotnet_diagnostic.CA2000.severity = none +# CA2201: Exception type System.Exception is not sufficiently specific +dotnet_diagnostic.CA2201.severity = none +# CA2208: Call the ArgumentNullException constructor that contains a message and/or paramName parameter +dotnet_diagnostic.CA2208.severity = none +# CA2227: Collection properties should be read only +dotnet_diagnostic.CA2227.severity = none +# CA5394 Random is an insecure random number generator +dotnet_diagnostic.CA5394.severity = none + +# AV1000: Type contains the word 'and', which suggests it has multiple purposes +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 +dotnet_diagnostic.AV1135.severity = none +# AV1505: Namespace should match with assembly name +dotnet_diagnostic.AV1505.severity = none +# AV1564: Parameter in public or internal member is of type bool or bool? +dotnet_diagnostic.AV1564.severity = none +# AV1708: Type name contains term that should be avoided +dotnet_diagnostic.AV1708.severity = none +# AV1225: Method raises event, so it should be named +dotnet_diagnostic.AV1225.severity = none +# AV1250; Method returns the result of a query, which uses deferred execution +dotnet_diagnostic.AV1250.severity = none +# AV1507: File contains additional type +dotnet_diagnostic.AV1507.severity = none +# AV1532: Loop statement contains nested loop +dotnet_diagnostic.AV1532.severity = none +# AV1555: Parameter in the call to is invoked with a named argument +dotnet_diagnostic.AV1555.severity = none +# AV1704: Type contains one or more digits in its name +dotnet_diagnostic.AV1704.severity = none +# AV1706: Parameter should have a more descriptive name +dotnet_diagnostic.AV1706.severity = none +# AV1710: Property contains the name of its containing type +dotnet_diagnostic.AV1710.severity = none +# AV1755: Name of async method should end with Async or TaskAsync +dotnet_diagnostic.AV1755.severity = none + +# SA0001: XmlCommentAnalysisDisabled +dotnet_diagnostic.SA0001.severity = none +# SA1001: CommasMustBeSpacedCorrectly +dotnet_diagnostic.SA1001.severity = none +# SA1009: Closing parenthesis should not be preceded by a space +dotnet_diagnostic.SA1009.severity = none +# SA1111: Closing parenthesis should be on line of the last parameter +dotnet_diagnostic.SA1111.severity = none +# SA1118: The parameter spans multiple lines +dotnet_diagnostic.SA1118.severity = none +# SA1122: Use string.Empty for empty strings +dotnet_diagnostic.SA1122.severity = none +# SA1124: Regions should not be used +dotnet_diagnostic.SA1124.severity = none +# SA1312: variable should begin with lower-case letter +dotnet_diagnostic.SA1312.severity = none # re-enable if using statements can be discarded +# SA1313: parameter should begin with lower-case letter +dotnet_diagnostic.SA1313.severity = none # re-enable when parameters discards are available +# SA1401: Field should be private +dotnet_diagnostic.SA1401.severity = none +# SA1402: File may only contain a single type +dotnet_diagnostic.SA1402.severity = none +# SA1403: File may only contain a single namespace +dotnet_diagnostic.SA1403.severity = none +# SA1404: Remove unused locals +dotnet_diagnostic.SA1404.severity = none +# SA1502: Element should not be on a single line +dotnet_diagnostic.SA1502.severity = none +# SA1600: Elements should be documented +dotnet_diagnostic.SA1600.severity = none +# SA1602: Enumeration items should be documented +dotnet_diagnostic.SA1602.severity = none +# SA1611: The documentation for parameter is missing +dotnet_diagnostic.SA1611.severity = none +# SA1615: Element return value should be documented +dotnet_diagnostic.SA1615.severity = none + +# SA1005: Single line comments should begin with single space +dotnet_diagnostic.SA1005.severity = suggestion + +# ReSharper/Rider +resharper_expression_is_always_null_highlighting=none diff --git a/Tests/FluentAssertions.Equivalency.Specs/AssertionRuleSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/AssertionRuleSpecs.cs new file mode 100644 index 0000000000..a7c523b2c8 --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/AssertionRuleSpecs.cs @@ -0,0 +1,274 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using FluentAssertions.Extensions; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Equivalency.Specs +{ + public class AssertionRuleSpecs + { + [Fact] + public void When_two_objects_have_the_same_property_values_it_should_succeed() + { + // Arrange + var subject = new { Age = 36, Birthdate = new DateTime(1973, 9, 20), Name = "Dennis" }; + + var other = new { Age = 36, Birthdate = new DateTime(1973, 9, 20), Name = "Dennis" }; + + // Act / Assert + subject.Should().BeEquivalentTo(other); + } + + [Fact] + public void When_two_objects_have_the_same_nullable_property_values_it_should_succeed() + { + // Arrange + var subject = new { Age = 36, Birthdate = (DateTime?)new DateTime(1973, 9, 20), Name = "Dennis" }; + + var other = new { Age = 36, Birthdate = (DateTime?)new DateTime(1973, 9, 20), Name = "Dennis" }; + + // Act / Assert + subject.Should().BeEquivalentTo(other); + } + + [Fact] + public void When_two_objects_have_the_same_properties_but_a_different_value_it_should_throw() + { + // Arrange + var subject = new { Age = 36 }; + + var expectation = new { Age = 37 }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, "because {0} are the same", "they"); + + // Assert + act.Should().Throw().WithMessage( + "Expected*Age*to be 37 because they are the same, but found 36*"); + } + + [Fact] + public void + When_subject_has_a_valid_property_that_is_compared_with_a_null_property_it_should_throw_with_descriptive_message() + { + // Arrange + var subject = new { Name = "Dennis" }; + + var other = new { Name = (string)null }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(other, "we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw().WithMessage( + "Expected property subject.Name to be *we want to test the failure message*, but found \"Dennis\"*"); + } + + [Fact] + public void When_two_collection_properties_dont_match_it_should_throw_and_specify_the_difference() + { + // Arrange + var subject = new { Values = new[] { 1, 2, 3 } }; + + var other = new { Values = new[] { 1, 4, 3 } }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(other); + + // Assert + act.Should().Throw().WithMessage( + "Expected*Values[1]*to be 4, but found 2*"); + } + + [Fact] + [SuppressMessage("ReSharper", "StringLiteralTypo")] + public void When_two_string_properties_do_not_match_it_should_throw_and_state_the_difference() + { + // Arrange + var subject = new { Name = "Dennes" }; + + var other = new { Name = "Dennis" }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(other, options => options.Including(d => d.Name)); + + // Assert + act.Should().Throw().WithMessage( + "Expected*Name to be \"Dennis\", but \"Dennes\" differs near \"es\" (index 4)*"); + } + + [Fact] + public void When_two_properties_are_of_derived_types_but_are_equal_it_should_succeed() + { + // Arrange + var subject = new { Type = new DerivedCustomerType("123") }; + + var other = new { Type = new CustomerType("123") }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(other); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void + When_two_properties_have_the_same_declared_type_but_different_runtime_types_and_are_equivalent_according_to_the_declared_type_it_should_succeed() + { + // Arrange + var subject = new { Type = (CustomerType)new DerivedCustomerType("123") }; + + var other = new { Type = new CustomerType("123") }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(other); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_nested_property_is_equal_based_on_equality_comparer_it_should_not_throw() + { + // Arrange + var subject = new { Timestamp = 22.March(2020).At(19, 30) }; + + var expectation = new { Timestamp = 1.January(2020).At(7, 31) }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, + opt => opt.Using()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_nested_property_is_unequal_based_on_equality_comparer_it_should_throw() + { + // Arrange + var subject = new { Timestamp = 22.March(2020) }; + + var expectation = new { Timestamp = 1.January(2021) }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, + opt => opt.Using(new DateTimeByYearComparer())); + + // Assert + act.Should() + .Throw() + .WithMessage("Expected*equal*2021*DateTimeByYearComparer*2020*"); + } + + [Fact] + public void When_the_subjects_property_type_is_different_from_the_equality_comparer_it_should_throw() + { + // Arrange + var subject = new { Timestamp = 1L }; + + var expectation = new { Timestamp = 1.January(2021) }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, + opt => opt.Using(new DateTimeByYearComparer())); + + // Assert + act.Should() + .Throw() + .WithMessage("Expected*Timestamp*1L*"); + } + + private class DateTimeByYearComparer : IEqualityComparer + { + public bool Equals(DateTime x, DateTime y) + { + return x.Year == y.Year; + } + + public int GetHashCode(DateTime obj) => obj.GetHashCode(); + } + + [Fact] + public void When_an_invalid_equality_comparer_is_provided_it_should_throw() + { + // Arrange + var subject = new { Timestamp = 22.March(2020) }; + + var expectation = new { Timestamp = 1.January(2021) }; + + IEqualityComparer equalityComparer = null; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, + opt => opt.Using(equalityComparer)); + + // Assert + act.Should() + .Throw() + .WithMessage("*comparer*"); + } + + [Fact] + public void When_the_compile_time_type_does_not_match_the_equality_comparer_type_it_should_use_the_default_mechanics() + { + // Arrange + var subject = new { Property = (IInterface)new ConcreteClass("SomeString") }; + + var expectation = new { Property = (IInterface)new ConcreteClass("SomeOtherString") }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, opt => + opt.Using()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_the_runtime_type_does_match_the_equality_comparer_type_it_should_use_the_default_mechanics() + { + // Arrange + var subject = new { Property = (IInterface)new ConcreteClass("SomeString") }; + + var expectation = new { Property = (IInterface)new ConcreteClass("SomeOtherString") }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, opt => opt + .RespectingRuntimeTypes() + .Using()); + + // Assert + act.Should().Throw().WithMessage("*ConcreteClassEqualityComparer*"); + } + + private interface IInterface + { + } + + private class ConcreteClass : IInterface + { + private readonly string property; + + public ConcreteClass(string propertyValue) + { + property = propertyValue; + } + + public string GetProperty() => property; + } + + private class ConcreteClassEqualityComparer : IEqualityComparer + { + public bool Equals(ConcreteClass x, ConcreteClass y) + { + return x.GetProperty() == y.GetProperty(); + } + + public int GetHashCode(ConcreteClass obj) => obj.GetProperty().GetHashCode(); + } + } +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/BasicEquivalencySpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/BasicEquivalencySpecs.cs deleted file mode 100644 index fd7d8027f6..0000000000 --- a/Tests/FluentAssertions.Equivalency.Specs/BasicEquivalencySpecs.cs +++ /dev/null @@ -1,5758 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Linq; -using System.Net; -using FluentAssertions.Common; -using FluentAssertions.Equivalency; -using FluentAssertions.Extensions; -using FluentAssertions.Specs.Primitives; -using Xunit; -using Xunit.Sdk; - -namespace FluentAssertions.Specs.Equivalency -{ - public class BasicEquivalencySpecs - { - #region General - - [Fact] - public void When_expectation_is_null_it_should_throw() - { - // Arrange - var subject = new - { - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(null); - - // Assert - act.Should().Throw().WithMessage( - "Expected subject to be , but found { }*"); - } - - [Fact] - 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" } } - }; - - var expectation = new[] - { - new MyClass() - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation); - - // Assert - act.Should().Throw().WithMessage( - "Expected*subject[0].Items*null*, but found*\"a\"*"); - } - - public class MyClass - { - public IEnumerable Items { get; set; } - } - - [Fact] - public void When_subject_is_null_it_should_throw() - { - // Arrange - SomeDto subject = null; - - // Act - Action act = () => subject.Should().BeEquivalentTo(new - { - }); - - // Assert - act.Should().Throw().WithMessage( - "Expected subject*to be*, but found *"); - } - - [Fact] - public void When_subject_and_expectation_are_null_it_should_not_throw() - { - // Arrange - SomeDto subject = null; - - // Act - Action act = () => subject.Should().BeEquivalentTo(null); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_subject_and_expectation_are_compared_for_equivalence_it_should_allow_chaining() - { - // Arrange - SomeDto subject = null; - - // Act - Action act = () => subject.Should().BeEquivalentTo(null) - .And.BeNull(); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_subject_and_expectation_are_compared_for_equivalence_with_config_it_should_allow_chaining() - { - // Arrange - SomeDto subject = null; - - // Act - Action act = () => subject.Should().BeEquivalentTo(null, opt => opt) - .And.BeNull(); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_subject_and_expectation_are_compared_for_non_equivalence_it_should_allow_chaining() - { - // Arrange - SomeDto subject = null; - - // Act - Action act = () => subject.Should().NotBeEquivalentTo(new { }) - .And.BeNull(); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_subject_and_expectation_are_compared_for_non_equivalence_with_config_it_should_allow_chaining() - { - // Arrange - SomeDto subject = null; - - // Act - Action act = () => subject.Should().NotBeEquivalentTo(new { }, opt => opt) - .And.BeNull(); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_equivalence_on_a_value_type_from_system_it_should_not_do_a_structural_comparision() - { - // Arrange - - // DateTime is used as an example because the current implementation - // would hit the recursion-depth limit if structural equivalence were attempted. - var date1 = new - { - Property = 1.January(2011) - }; - - var date2 = new - { - Property = 1.January(2011) - }; - - // Act - Action act = () => date1.Should().BeEquivalentTo(date2); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_an_object_hides_object_equals_it_should_be_compared_using_its_members() - { - // Arrange - var actual = new VirtualClassOverride - { - Property = "Value", - OtherProperty = "Actual" - }; - - var expected = new VirtualClassOverride - { - Property = "Value", - OtherProperty = "Expected" - }; - - // Act - Action act = () => actual.Should().BeEquivalentTo(expected); - - // Assert - act.Should().Throw("*OtherProperty*Expected*Actual*"); - } - - public class VirtualClass - { - public string Property { get; set; } - - public new virtual bool Equals(object obj) - { - return (obj is VirtualClass other) && other.Property == Property; - } - } - - public class VirtualClassOverride : VirtualClass - { - public string OtherProperty { get; set; } - } - - [Fact] - 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" - } - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected, - options => options.ComparingByMembers()); - - // Assert - act.Should().Throw().WithMessage("*NestedProperty*OtherValue*SomeValue*"); - } - - [Fact] - public void When_treating_a_value_type_as_a_complex_type_it_should_compare_them_by_members() - { - // Arrange - var subject = new ClassWithValueSemanticsOnSingleProperty - { - Key = "SameKey", - NestedProperty = "SomeValue" - }; - - var expected = new ClassWithValueSemanticsOnSingleProperty - { - Key = "SameKey", - NestedProperty = "OtherValue" - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected, - options => options.ComparingByMembers()); - - // Assert - act.Should().Throw().WithMessage("*NestedProperty*OtherValue*SomeValue*"); - } - - [Fact] - public void When_treating_a_type_as_value_type_but_it_was_already_marked_as_reference_type_it_should_throw() - { - // Arrange - var subject = new ClassWithValueSemanticsOnSingleProperty - { - Key = "Don't care" - }; - - var expected = new ClassWithValueSemanticsOnSingleProperty - { - Key = "Don't care" - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected, options => options - .ComparingByMembers() - .ComparingByValue()); - - // Assert - act.Should().Throw().WithMessage( - $"*compare {nameof(ClassWithValueSemanticsOnSingleProperty)}*value*already*members*"); - } - - [Fact] - public void When_treating_a_type_as_reference_type_but_it_was_already_marked_as_value_type_it_should_throw() - { - // Arrange - var subject = new ClassWithValueSemanticsOnSingleProperty - { - Key = "Don't care" - }; - - var expected = new ClassWithValueSemanticsOnSingleProperty - { - Key = "Don't care" - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected, options => options - .ComparingByValue() - .ComparingByMembers()); - - // Assert - act.Should().Throw().WithMessage( - $"*compare {nameof(ClassWithValueSemanticsOnSingleProperty)}*members*already*value*"); - } - - [Fact] - public void When_treating_a_complex_type_in_a_collection_as_a_value_type_it_should_compare_them_by_value() - { - // Arrange - var subject = new[] - { - new - { - Address = IPAddress.Parse("1.2.3.4"), - Word = "a" - } - }; - - var expected = new[] - { - new - { - Address = IPAddress.Parse("1.2.3.4"), - Word = "a" - } - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected, - options => options.ComparingByValue()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_treating_a_complex_type_as_a_value_type_it_should_compare_them_by_value() - { - // Arrange - var subject = new - { - Address = IPAddress.Parse("1.2.3.4"), - Word = "a" - }; - - var expected = new - { - Address = IPAddress.Parse("1.2.3.4"), - Word = "a" - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected, - options => options.ComparingByValue()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_treating_a_null_type_as_value_type_it_should_throw() - { - // Arrange - var subject = new object(); - var expected = new object(); - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected, opt => opt - .ComparingByValue(null)); - - // Assert - act.Should().Throw() - .WithParameterName("type"); - } - - [Fact] - public void When_treating_a_null_type_as_reference_type_it_should_throw() - { - // Arrange - var subject = new object(); - var expected = new object(); - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected, opt => opt - .ComparingByMembers(null)); - - // Assert - act.Should().Throw() - .WithParameterName("type"); - } - - [Fact] - 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 }); - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected, opt => opt - .ComparingByMembers(typeof(Option<>))); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - 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 }); - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected, opt => opt - .ComparingByMembers(typeof(Option<>)) - .ComparingByValue>()); - - // Assert - act.Should().Throw(); - } - - [Fact] - 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 }); - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected, opt => opt - .ComparingByValue(typeof(Option<>)) - .ComparingByMembers>()); - - // Assert - act.Should().NotThrow(); - } - - private struct Option : IEquatable> - where T : class - { - public T Value { get; } - - public Option(T value) - { - Value = value; - } - - public bool Equals(Option other) => - EqualityComparer.Default.Equals(Value, other.Value); - - public override bool Equals(object obj) => - obj is Option other && Equals(other); - - public override int GetHashCode() => Value?.GetHashCode() ?? 0; - } - - [Fact] - public void When_threating_any_type_as_reference_type_it_should_exclude_primitive_types() - { - // Arrange - var subject = new { Value = 1 }; - var expected = new { Value = 2 }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected, opt => opt - .ComparingByMembers()); - - // Assert - act.Should().Throw() - .WithMessage("*be 2*found 1*"); - } - - [Fact] - public void When_threating_an_open_type_as_reference_type_it_should_exclude_primitive_types() - { - // Arrange - var subject = new { Value = 1 }; - var expected = new { Value = 2 }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected, opt => opt - .ComparingByMembers(typeof(IEquatable<>))); - - // Assert - act.Should().Throw() - .WithMessage("*be 2*found 1*"); - } - - [Fact] - public void When_threating_a_primitive_type_as_a_reference_type_it_should_throw() - { - // Arrange - var subject = new { Value = 1 }; - var expected = new { Value = 2 }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected, opt => opt - .ComparingByMembers()); - - // Assert - act.Should().Throw() - .WithMessage("*Cannot compare a primitive type*Int32*"); - } - - [Fact] - public void When_a_type_originates_from_the_System_namespace_it_should_be_treated_as_a_value_type() - { - // Arrange - var subject = new - { - UriBuilder = new UriBuilder("http://localhost:9001/api"), - }; - - var expected = new - { - UriBuilder = new UriBuilder("https://localhost:9002/bapi"), - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected); - - // Assert - act.Should().Throw() - .WithMessage("Expected*UriBuilder* to be https://localhost:9002/bapi, but found http://localhost:9001/api*"); - } - - [Fact] - public void When_asserting_equivalence_on_a_string_it_should_use_string_specific_failure_messages() - { - // Arrange - string s1 = "hello"; - string s2 = "good-bye"; - - // Act - Action act = () => s1.Should().BeEquivalentTo(s2); - - // Assert - act.Should().Throw() - .WithMessage("*to be*\"good-bye\" with a length of 8, but \"hello\" has a length of 5*"); - } - - [Fact] - public void When_asserting_equivalence_of_strings_typed_as_objects_it_should_compare_them_as_strings() - { - // Arrange - - // The convoluted construction is so the compiler does not optimize the two objects to be the same. - object s1 = new string('h', 2); - object s2 = "hh"; - - // Act - Action act = () => s1.Should().BeEquivalentTo(s2); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_equivalence_of_ints_typed_as_objects_it_should_use_the_runtime_type() - { - // Arrange - object s1 = 1; - object s2 = 1; - - // Act - Action act = () => s1.Should().BeEquivalentTo(s2); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_all_field_of_the_object_are_equal_equivalency_should_pass() - { - // Arrange - var object1 = new ClassWithOnlyAField { Value = 1 }; - var object2 = new ClassWithOnlyAField { Value = 1 }; - - // Act - Action act = () => object1.Should().BeEquivalentTo(object2); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_number_values_are_convertible_it_should_treat_them_as_equivalent() - { - // Arrange - var actual = new Dictionary - { - ["001"] = 1L, - ["002"] = 2L - }; - - var expected = new Dictionary - { - ["001"] = 1, - ["002"] = 2 - }; - - // Act - Action act = () => actual.Should().BeEquivalentTo(expected); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_all_field_of_the_object_are_not_equal_equivalency_should_fail() - { - // Arrange - var object1 = new ClassWithOnlyAField { Value = 1 }; - var object2 = new ClassWithOnlyAField { Value = 101 }; - - // Act - Action act = () => object1.Should().BeEquivalentTo(object2); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_field_on_the_subject_matches_a_property_the_members_should_match_for_equivalence() - { - // Arrange - var onlyAField = new ClassWithOnlyAField { Value = 1 }; - var onlyAProperty = new ClassWithOnlyAProperty { Value = 101 }; - - // Act - Action act = () => onlyAField.Should().BeEquivalentTo(onlyAProperty); - - // Assert - act.Should().Throw().WithMessage("Expected property onlyAField.Value*to be 101, but found 1.*"); - } - - [Fact] - public void When_asserting_equivalence_including_only_fields_it_should_not_match_properties() - { - // Arrange - var onlyAField = new ClassWithOnlyAField { Value = 1 }; - object onlyAProperty = new ClassWithOnlyAProperty { Value = 101 }; - - // Act - Action act = () => onlyAProperty.Should().BeEquivalentTo(onlyAField, opts => opts.ExcludingProperties()); - - // Assert - act.Should().Throw() - .WithMessage("Expectation has field onlyAProperty.Value that the other object does not have.*"); - } - - [Fact] - public void When_asserting_equivalence_including_only_properties_it_should_not_match_fields() - { - // Arrange - var onlyAField = new ClassWithOnlyAField { Value = 1 }; - var onlyAProperty = new ClassWithOnlyAProperty { Value = 101 }; - - // Act - Action act = () => onlyAField.Should().BeEquivalentTo(onlyAProperty, opts => opts.IncludingAllDeclaredProperties()); - - // Assert - act.Should().Throw() - .WithMessage("Expectation has property onlyAField.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() - { - // Arrange - var record = new - { - Member1 = "", - Member2 = new[] { "", "" } - }; - - var record2 = new - { - Member1 = "different", - Member2 = new[] { "", "" } - }; - - // Act - Action act = () => record.Should().BeEquivalentTo(record2); - - // Assert - act.Should().Throw().WithMessage( - @"Expected property record.Member1* to be*""different"" with a length of 9, but*"""" has a length of 0*"); - } - - [Fact] - public void When_asserting_object_equivalence_against_a_null_value_it_should_properly_throw() - { - // Act - Action act = () => ((object)null).Should().BeEquivalentTo("foo"); - - // Assert - act.Should().Throw().WithMessage("*foo*null*"); - } - - [Fact] - public void When_the_graph_contains_guids_it_should_properly_format_them() - { - // Arrange - var actual = - new[] - { - new { Id = Guid.NewGuid(), Name = "Name" } - }; - - var expected = - new[] - { - new { Id = Guid.NewGuid(), Name = "Name" } - }; - - // Act - Action act = () => actual.Should().BeEquivalentTo(expected); - - // Assert - act.Should().Throw().WithMessage("Expected property actual[0].Id*to be *-*, but found *-*"); - } - - #endregion - - #region Selection Rules - - public enum LocalOtherType : byte - { - Default, - NonDefault - } - - public enum LocalType : byte - { - Default, - NonDefault - } - - [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 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(); - } - - public class CustomType - { - public string Name { get; set; } - } - - public class ClassA - { - public List ListOfCustomTypes { 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 CustomType { Name = "A" }, - new CustomType { Name = "B" } - }; - - var list2 = new List - { - new CustomType { Name = "C" }, - new CustomType { 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*"); - } - - [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(_ => _.Field1).Including(_ => _.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(_ => _.Field1).Including(_ => _.Field2).Including(_ => _.Field3)); - - // Assert - act.Should().Throw().WithMessage("Expected field class1.Field3*"); - } - - [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] - [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(_ => _.Field3).Excluding(_ => _.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(_ => _.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_property_is_write_only_it_should_be_ignored() - { - // Arrange - var subject = new ClassWithWriteOnlyProperty - { - WriteOnlyProperty = 123, - SomeOtherProperty = "whatever" - }; - - var expected = 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_hidden_in_a_derived_class_it_should_ignore_it() - { - // Arrange - var subject = new SubclassA { Foo = "test" }; - var expectation = new SubclassB { Foo = "test" }; - - // Act - Action action = () => subject.Should().BeEquivalentTo(expectation); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void When_including_a_property_that_is_hidden_in_a_derived_class_it_should_select_the_correct_one() - { - // Arrange - var b1 = new ClassThatHidesBaseClassProperty(); - var b2 = new ClassThatHidesBaseClassProperty(); - - // Act / Assert - b1.Should().BeEquivalentTo(b2, config => config.Including(b => b.Property)); - } - - [Fact] - public void Excluding_a_property_hiding_a_base_class_property_should_not_reveal_the_latter() - { - // Arrange - var b1 = new ClassThatHidesBaseClassProperty(); - var b2 = new ClassThatHidesBaseClassProperty(); - - // Act - Action act = () => b1.Should().BeEquivalentTo(b2, config => config.Excluding(b => b.Property)); - - // Assert - act.Should().Throw().WithMessage("*No members were found *"); - } - - private class ClassWithGuidProperty - { - public string Property { get; set; } = Guid.NewGuid().ToString(); - } - - private class ClassThatHidesBaseClassProperty : ClassWithGuidProperty - { - public new string[] Property { get; set; } - } - - [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 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 - { - public string PublicProperty { get; set; } - - internal string InternalProperty { get; set; } - - 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 - { - public string PublicField; - - internal string InternalField; - - protected internal string ProtectedInternalField; - } - - [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 BaseWithFoo - { - public object Foo { get; set; } - } - - public class SubclassA : BaseWithFoo - { - public new T Foo - { - get { return (T)base.Foo; } - - set { base.Foo = value; } - } - } - - public class D - { - public object Foo { get; set; } - } - - public class SubclassB : D - { - public new T Foo - { - get { return (T)base.Foo; } - - set { base.Foo = value; } - } - } - - public class ClassWithIndexer - { - public object Foo { get; set; } - - public string this[int n] - { - get - { - return - n.ToString( - CultureInfo.InvariantCulture); - } - } - } - - [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 When_respecting_declared_types_explicit_interface_member_on_interfaced_subject_should_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.RespectingDeclaredTypes()); - - // Assert - action.Should().NotThrow(); - } - - [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_subject_should_not_be_used() - { - // Arrange - var expected = new Vehicle - { - VehicleId = 1 - }; - - var subject = new ExplicitVehicle - { - VehicleId = 2 - }; - ((IVehicle)subject).VehicleId = 1; - - // Act - Action action = () => subject.Should().BeEquivalentTo(expected, opt => opt.RespectingDeclaredTypes()); - - // 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, opt => opt.RespectingDeclaredTypes()); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_respecting_runtime_types_explicit_interface_member_on_subject_should_not_be_used() - { - // Arrange - var expected = new Vehicle - { - VehicleId = 1 - }; - - var subject = new ExplicitVehicle - { - VehicleId = 2 - }; - ((IVehicle)subject).VehicleId = 1; - - // 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_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, opt => opt.RespectingRuntimeTypes()); - - // Assert - action.Should().Throw(); - } - - [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_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", - "ignore-private-protected"); - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected, config => - config.Excluding(ctx => ctx.WhichGetterHas(CSharpAccessModifier.Internal) || - ctx.WhichGetterHas(CSharpAccessModifier.ProtectedInternal) || - ctx.WhichGetterHas(CSharpAccessModifier.PrivateProtected))); - - // 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", - "ignore-private-protected"); - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected, config => - config.Excluding(ctx => ctx.WhichSetterHas(CSharpAccessModifier.Internal) || - ctx.WhichSetterHas(CSharpAccessModifier.ProtectedInternal) || - ctx.WhichSetterHas(CSharpAccessModifier.Private) || - ctx.WhichSetterHas(CSharpAccessModifier.PrivateProtected))); - - // Assert - act.Should().NotThrow(); - } - - [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 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 ClassWithOnlyAProperty { Value = 1 }, - ["Bar"] = new ClassWithOnlyAProperty { Value = 2 } - } - }; - - var expected = new - { - List = new[] { new { Foo = 1, Bar = 2 }, new { Foo = 2, Bar = 4 } }.ToList(), - Dictionary = new Dictionary - { - ["Foo"] = new ClassWithOnlyAProperty { Value = 1 }, - ["Bar"] = new ClassWithOnlyAProperty { 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_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 ClassWithOnlyAProperty { Value = 1 }, - ["Bar"] = new ClassWithOnlyAProperty { Value = 2 } - } - }; - - var expected = new - { - List = new[] { new { Foo = 5, Bar = 2 }, new { Foo = 2, Bar = 4 } }.ToList(), - Dictionary = new Dictionary - { - ["Foo"] = new ClassWithOnlyAProperty { Value = 6 }, - ["Bar"] = new ClassWithOnlyAProperty { 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] - [SuppressMessage("ReSharper", "StringLiteralTypo")] - public void When_using_IncludingAllDeclaredProperties_fields_should_be_ignored() - { - // 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 When_using_IncludingAllRuntimeProperties_the_runtime_type_should_be_used_and_fields_should_be_ignored() - { - // 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 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] - [SuppressMessage("ReSharper", "StringLiteralTypo")] - public void When_respecting_the_runtime_type_is_configured_the_runtime_type_should_be_used_and_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 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 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 - { - public string Name { get; set; } - - public ClassWithoutProperty ClassWithoutProperty { get; } = new ClassWithoutProperty(); - } - - internal class ClassWithoutProperty - { - } - -#if NET5_0_OR_GREATER - - [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 - { - public string BaseProperty { get; set; } - } - - private class DerivedWithProperty : BaseWithProperty - { - public string DerivedProperty { get; set; } - } - - private abstract class BaseWithAbstractProperty - { - public abstract BaseWithProperty Property { get; } - } - - private sealed class DerivedWithCovariantOverride : BaseWithAbstractProperty - { - public override DerivedWithProperty Property { get; } - - public string OtherProp { get; set; } - - public DerivedWithCovariantOverride(DerivedWithProperty prop) - { - Property = prop; - } - } - -#endif - - 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 - { - } - - [Fact] - public void Excluding_an_interface_property_through_inheritance_should_work() - { - // Arrange - var actual = new IInterfaceWithTwoProperties[] - { - new DerivedClassImplementingInterface { Value1 = 1, Value2 = 2 } - }; - - var expected = new IInterfaceWithTwoProperties[] - { - 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 - var actual = new IInterfaceWithTwoProperties[] - { - new DerivedClassImplementingInterface { Value1 = 1, Value2 = 2 } - }; - - var expected = new IInterfaceWithTwoProperties[] - { - new DerivedClassImplementingInterface { Value1 = 999, Value2 = 2 } - }; - - // Act / Assert - actual.Should().BeEquivalentTo(expected, options => options - .Including(a => a.Value2) - .RespectingRuntimeTypes()); - } - - #endregion - - #region Matching Rules - - [Fact] - [SuppressMessage("ReSharper", "StringLiteralTypo")] - public void When_using_ExcludingMissingMembers_both_fields_and_properties_should_be_ignored() - { - // Arrange - var class1 = new ClassWithSomeFieldsAndProperties - { - Field1 = "Lorem", - Field2 = "ipsum", - Field3 = "dolor", - Property1 = "sit", - Property2 = "amet", - Property3 = "consectetur" - }; - - var class2 = new - { - Field1 = "Lorem" - }; - - // Act - Action act = - () => class1.Should().BeEquivalentTo(class2, opts => opts.ExcludingMissingMembers()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_property_shared_by_anonymous_types_doesnt_match_it_should_throw() - { - // Arrange - var subject = new - { - Age = 36 - }; - - var other = new - { - Age = 37 - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(other, options => options.ExcludingMissingMembers()); - - // Assert - act.Should().Throw(); - } - - #endregion - - #region DateTime Properties - - [Fact] - public void When_two_properties_are_datetime_and_both_are_nullable_and_both_are_null_it_should_succeed() - { - // Arrange - var subject = - new - { - Time = (DateTime?)null - }; - - var other = - new - { - Time = (DateTime?)null - }; - - // Act - Action act = () => - subject.Should().BeEquivalentTo(other); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_two_properties_are_datetime_and_both_are_nullable_and_are_equal_it_should_succeed() - { - // Arrange - var subject = - new - { - Time = (DateTime?)new DateTime(2013, 12, 9, 15, 58, 0) - }; - - var other = - new - { - Time = (DateTime?)new DateTime(2013, 12, 9, 15, 58, 0) - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(other); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_two_properties_are_datetime_and_both_are_nullable_and_expectation_is_null_it_should_throw_and_state_the_difference() - { - // Arrange - var subject = - new - { - Time = (DateTime?)new DateTime(2013, 12, 9, 15, 58, 0) - }; - - var other = - new - { - Time = (DateTime?)null - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(other); - - // Assert - act.Should().Throw().WithMessage( - "Expected*Time to be , but found <2013-12-09 15:58:00>.*"); - } - - [Fact] - public void - When_two_properties_are_datetime_and_both_are_nullable_and_subject_is_null_it_should_throw_and_state_the_difference() - { - // Arrange - var subject = - new - { - Time = (DateTime?)null - }; - - var other = - new - { - Time = (DateTime?)new DateTime(2013, 12, 9, 15, 58, 0) - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(other); - - // Assert - act.Should().Throw().WithMessage( - "Expected*Time*to be <2013-12-09 15:58:00>, but found .*"); - } - - [Fact] - public void When_two_properties_are_datetime_and_expectation_is_nullable_and_are_equal_it_should_succeed() - { - // Arrange - var subject = - new - { - Time = new DateTime(2013, 12, 9, 15, 58, 0) - }; - - var other = - new - { - Time = (DateTime?)new DateTime(2013, 12, 9, 15, 58, 0) - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(other); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_two_properties_are_datetime_and_expectation_is_nullable_and_expectation_is_null_it_should_throw_and_state_the_difference() - { - // Arrange - var subject = - new - { - Time = new DateTime(2013, 12, 9, 15, 58, 0) - }; - - var other = - new - { - Time = (DateTime?)null - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(other); - - // Assert - act.Should().Throw().WithMessage( - "Expected*Time*to be , but found <2013-12-09 15:58:00>.*"); - } - - [Fact] - public void When_two_properties_are_datetime_and_subject_is_nullable_and_are_equal_it_should_succeed() - { - // Arrange - var subject = - new - { - Time = (DateTime?)new DateTime(2013, 12, 9, 15, 58, 0) - }; - - var other = - new - { - Time = new DateTime(2013, 12, 9, 15, 58, 0) - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(other); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void - When_two_properties_are_datetime_and_subject_is_nullable_and_subject_is_null_it_should_throw_and_state_the_difference() - { - // Arrange - var subject = - new - { - Time = (DateTime?)null - }; - - var other = - new - { - Time = new DateTime(2013, 12, 9, 15, 58, 0) - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(other); - - // Assert - act.Should().Throw().WithMessage( - "Expected*Time*to be <2013-12-09 15:58:00>, but found .*"); - } - - #endregion - - #region Assertion Rules - - [Fact] - public void When_two_objects_have_the_same_property_values_it_should_succeed() - { - // Arrange - var subject = new - { - Age = 36, - Birthdate = new DateTime(1973, 9, 20), - Name = "Dennis" - }; - - var other = new - { - Age = 36, - Birthdate = new DateTime(1973, 9, 20), - Name = "Dennis" - }; - - // Act / Assert - subject.Should().BeEquivalentTo(other); - } - - [Fact] - public void When_two_objects_have_the_same_nullable_property_values_it_should_succeed() - { - // Arrange - var subject = new - { - Age = 36, - Birthdate = (DateTime?)new DateTime(1973, 9, 20), - Name = "Dennis" - }; - - var other = new - { - Age = 36, - Birthdate = (DateTime?)new DateTime(1973, 9, 20), - Name = "Dennis" - }; - - // Act / Assert - subject.Should().BeEquivalentTo(other); - } - - [Fact] - public void When_two_objects_have_the_same_properties_but_a_different_value_it_should_throw() - { - // Arrange - var subject = new - { - Age = 36 - }; - - var expectation = new - { - Age = 37 - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation, "because {0} are the same", "they"); - - // Assert - act.Should().Throw().WithMessage( - "Expected*Age*to be 37 because they are the same, but found 36*"); - } - - [Fact] - public void When_subject_has_a_valid_property_that_is_compared_with_a_null_property_it_should_throw_with_descriptive_message() - { - // Arrange - var subject = new - { - Name = "Dennis" - }; - - var other = new - { - Name = (string)null - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(other, "we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw().WithMessage( - "Expected property subject.Name to be *we want to test the failure message*, but found \"Dennis\"*"); - } - - [Fact] - public void When_two_collection_properties_dont_match_it_should_throw_and_specify_the_difference() - { - // Arrange - var subject = new - { - Values = new[] - { - 1, 2, 3 - } - }; - - var other = new - { - Values = new[] - { - 1, 4, 3 - } - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(other); - - // Assert - act.Should().Throw().WithMessage( - "Expected*Values[1]*to be 4, but found 2*"); - } - - [Fact] - [SuppressMessage("ReSharper", "StringLiteralTypo")] - public void When_two_string_properties_do_not_match_it_should_throw_and_state_the_difference() - { - // Arrange - var subject = new - { - Name = "Dennes" - }; - - var other = new - { - Name = "Dennis" - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(other, options => options.Including(d => d.Name)); - - // Assert - act.Should().Throw().WithMessage( - "Expected*Name to be \"Dennis\", but \"Dennes\" differs near \"es\" (index 4)*"); - } - - [Fact] - public void When_two_properties_are_of_derived_types_but_are_equal_it_should_succeed() - { - // Arrange - var subject = new - { - Type = new DerivedCustomerType("123") - }; - - var other = new - { - Type = new CustomerType("123") - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(other); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_two_properties_have_the_same_declared_type_but_different_runtime_types_and_are_equivalent_according_to_the_declared_type_it_should_succeed() - { - // Arrange - var subject = new - { - Type = (CustomerType)new DerivedCustomerType("123") - }; - - var other = new - { - Type = new CustomerType("123") - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(other); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_nested_property_is_equal_based_on_equality_comparer_it_should_not_throw() - { - // Arrange - var subject = new - { - Timestamp = 22.March(2020).At(19, 30) - }; - - var expectation = new - { - Timestamp = 1.January(2020).At(7, 31) - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation, - opt => opt.Using()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_nested_property_is_unequal_based_on_equality_comparer_it_should_throw() - { - // Arrange - var subject = new - { - Timestamp = 22.March(2020) - }; - - var expectation = new - { - Timestamp = 1.January(2021) - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation, - opt => opt.Using(new DateTimeByYearComparer())); - - // Assert - act.Should() - .Throw() - .WithMessage("Expected*equal*2021*DateTimeByYearComparer*2020*"); - } - - [Fact] - public void When_the_subjects_property_type_is_different_from_the_equality_comparer_it_should_throw() - { - // Arrange - var subject = new - { - Timestamp = 1L - }; - - var expectation = new - { - Timestamp = 1.January(2021) - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation, - opt => opt.Using(new DateTimeByYearComparer())); - - // Assert - act.Should() - .Throw() - .WithMessage("Expected*Timestamp*1L*"); - } - - private class DateTimeByYearComparer : IEqualityComparer - { - public bool Equals(DateTime x, DateTime y) - { - return x.Year == y.Year; - } - - public int GetHashCode(DateTime obj) => obj.GetHashCode(); - } - - [Fact] - public void When_an_invalid_equality_comparer_is_provided_it_should_throw() - { - // Arrange - var subject = new - { - Timestamp = 22.March(2020) - }; - - var expectation = new - { - Timestamp = 1.January(2021) - }; - - IEqualityComparer equalityComparer = null; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation, - opt => opt.Using(equalityComparer)); - - // Assert - act.Should() - .Throw() - .WithMessage("*comparer*"); - } - - [Fact] - public void When_the_compile_time_type_does_not_match_the_equality_comparer_type_it_should_use_the_default_mechanics() - { - // Arrange - var subject = new - { - Property = (IInterface)new ConcreteClass("SomeString") - }; - - var expectation = new - { - Property = (IInterface)new ConcreteClass("SomeOtherString") - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation, opt => - opt.Using()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_the_runtime_type_does_match_the_equality_comparer_type_it_should_use_the_default_mechanics() - { - // Arrange - var subject = new - { - Property = (IInterface)new ConcreteClass("SomeString") - }; - - var expectation = new - { - Property = (IInterface)new ConcreteClass("SomeOtherString") - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation, opt => opt - .RespectingRuntimeTypes() - .Using()); - - // Assert - act.Should().Throw().WithMessage("*ConcreteClassEqualityComparer*"); - } - - private interface IInterface - { - } - - private class ConcreteClass : IInterface - { - private readonly string property; - - public ConcreteClass(string propertyValue) - { - property = propertyValue; - } - - public string GetProperty() => property; - } - - private class ConcreteClassEqualityComparer : IEqualityComparer - { - public bool Equals(ConcreteClass x, ConcreteClass y) - { - return x.GetProperty() == y.GetProperty(); - } - - public int GetHashCode(ConcreteClass obj) => obj.GetProperty().GetHashCode(); - } - - #endregion - - #region Member Conversion - - [Fact] - public void When_two_objects_have_the_same_properties_with_convertable_values_it_should_succeed() - { - // Arrange - var subject = new - { - Age = "37", - Birthdate = "1973-09-20" - }; - - var other = new - { - Age = 37, - Birthdate = new DateTime(1973, 9, 20) - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(other, o => o.WithAutoConversion()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_string_is_declared_equivalent_to_an_int_representing_the_numerals_it_should_pass() - { - // Arrange - var actual = new - { - Property = "32" - }; - - var expectation = new - { - Property = 32 - }; - - // Act - Action act = () => actual.Should().BeEquivalentTo(expectation, - options => options.WithAutoConversion()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_an_int_is_compared_equivalent_to_a_string_representing_the_number_it_should_pass() - { - // Arrange - var subject = new { Property = 32 }; - var expectation = new { Property = "32" }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation, options => options.WithAutoConversion()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_injecting_a_null_predicate_into_WithAutoConversionFor_it_should_throw() - { - // Arrange - var subject = new object(); - - var expectation = new object(); - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation, - options => options.WithAutoConversionFor(predicate: null)); - - // Assert - act.Should().ThrowExactly() - .WithParameterName("predicate"); - } - - [Fact] - public void When_only_a_single_property_is_and_can_be_converted_but_the_other_one_doesnt_match_it_should_throw() - { - // Arrange - var subject = new - { - Age = 32, - Birthdate = "1973-09-20" - }; - - var expectation = new - { - Age = "32", - Birthdate = new DateTime(1973, 9, 20) - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation, - options => options.WithAutoConversionFor(x => x.Path.Contains("Birthdate"))); - - // Assert - act.Should().Throw().WithMessage("*Age*String*Int32*"); - } - - [Fact] - public void When_only_a_single_property_is_converted_and_the_other_matches_it_should_succeed() - { - // Arrange - var subject = new - { - Age = 32, - Birthdate = "1973-09-20" - }; - - var expectation = new - { - Age = 32, - Birthdate = new DateTime(1973, 9, 20) - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation, options => options - .WithAutoConversionFor(x => x.Path.Contains("Birthdate"))); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_injecting_a_null_predicate_into_WithoutAutoConversionFor_it_should_throw() - { - // Arrange - var subject = new object(); - - var expectation = new object(); - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation, - options => options.WithoutAutoConversionFor(predicate: null)); - - // Assert - act.Should().ThrowExactly() - .WithParameterName("predicate"); - } - - [Fact] - public void When_a_specific_mismatching_property_is_excluded_from_conversion_it_should_throw() - { - // Arrange - var subject = new - { - Age = 32, - Birthdate = "1973-09-20" - }; - - var expectation = new - { - Age = 32, - Birthdate = new DateTime(1973, 9, 20) - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation, options => options - .WithAutoConversion() - .WithoutAutoConversionFor(x => x.Path.Contains("Birthdate"))); - - // Assert - act.Should().Throw().Which.Message - .Should().Match("Expected*<1973-09-20>*\"1973-09-20\"*", "{0} field is of mismatched type", nameof(expectation.Birthdate)) - .And.Subject.Should().Match("*Try conversion of all members*", "conversion description should be present") - .And.Subject.Should().NotMatch("*Try conversion of all members*Try conversion of all members*", "conversion description should not be duplicated"); - } - - [Fact] - public void When_declaring_equivalent_a_convertable_object_that_is_equivalent_once_converted_it_should_pass() - { - // Arrange - string str = "This is a test"; - CustomConvertible obj = new CustomConvertible(str); - - // Act - Action act = () => obj.Should().BeEquivalentTo(str, options => options.WithAutoConversion()); - - // Assert - act.Should().NotThrow(); - } - - #endregion - - #region Nested Properties - - [Fact] - public void When_all_the_properties_of_the_nested_objects_are_equal_it_should_succeed() - { - // Arrange - var subject = new Root - { - Text = "Root", - Level = new Level1 - { - Text = "Level1", - Level = new Level2 - { - Text = "Level2" - } - } - }; - - var expected = new RootDto - { - Text = "Root", - Level = new Level1Dto - { - Text = "Level1", - Level = new Level2Dto - { - Text = "Level2" - } - } - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_the_expectation_contains_a_nested_null_it_should_properly_report_the_difference() - { - // Arrange - var subject = new Root - { - Text = "Root", - Level = new Level1 - { - Text = "Level1", - Level = new Level2() - } - }; - - var expected = new RootDto - { - Text = "Root", - Level = new Level1Dto - { - Text = "Level1", - Level = null - } - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected); - - // Assert - act.Should().Throw() - .WithMessage("*Expected*Level.Level*to be , but found*Level2*Without automatic conversion*"); - } - - [Fact] - public void When_not_all_the_properties_of_the_nested_objects_are_equal_but_nested_objects_are_excluded_it_should_succeed() - { - // Arrange - var subject = new - { - Property = new ClassWithValueSemanticsOnSingleProperty - { - Key = "123", - NestedProperty = "Should be ignored" - } - }; - - var expected = new - { - Property = new ClassWithValueSemanticsOnSingleProperty - { - Key = "123", - NestedProperty = "Should be ignored as well" - } - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected, - options => options.ExcludingNestedObjects()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_nested_objects_should_be_excluded_it_should_do_a_simple_equality_check_instead() - { - // Arrange - var item = new Item - { - Child = new Item() - }; - - // Act - Action act = () => item.Should().BeEquivalentTo(new Item(), options => options.ExcludingNestedObjects()); - - // Assert - act.Should().Throw() - .WithMessage("Expected*Item*null*"); - } - - public class Item - { - public Item Child { get; set; } - } - - [Fact] - public void When_not_all_the_properties_of_the_nested_objects_are_equal_it_should_throw() - { - // Arrange - var subject = new Root - { - Text = "Root", - Level = new Level1 - { - Text = "Level1" - } - }; - - var expected = new RootDto - { - Text = "Root", - Level = new Level1Dto - { - Text = "Level2" - } - }; - - // Act - Action act = () => - subject.Should().BeEquivalentTo(expected); - - // Assert - act.Should().Throw().Which.Message - - // Checking exception message exactly is against general guidelines - // but in that case it was done on purpose, so that we have at least have a single - // test confirming that whole mechanism of gathering description from - // equivalency steps works. - .Should().Match(@"Expected property subject.Level.Text to be ""Level2"", but ""Level1"" differs near ""1"" (index 5).*" + - "With configuration:*" + - "- Use declared types and 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)*" + - "- Be strict about the order of items in byte arrays*" + - "- Without automatic conversion.*"); - } - - [Fact] - public void When_the_actual_nested_object_is_null_it_should_throw() - { - // Arrange - var subject = new Root - { - Text = "Root", - Level = new Level1 - { - Text = "Level2" - } - }; - - var expected = new RootDto - { - Text = "Root", - Level = null - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected); - - // Assert - act - .Should().Throw() - .WithMessage("Expected*Level*to be *, but found*Level1*Level2*"); - } - - public class StringSubContainer - { - public string SubValue { get; set; } - } - - public class StringContainer - { - public StringContainer(string mainValue, string subValue = null) - { - MainValue = mainValue; - SubValues = new[] - { - new StringSubContainer - { - SubValue = subValue - } - }; - } - - public string MainValue { get; set; } - - public IList SubValues { get; set; } - } - - public class MyClass2 - { - public StringContainer One { get; set; } - - public StringContainer Two { get; set; } - } - - [Fact] - public void When_deeply_nested_strings_dont_match_it_should_properly_report_the_mismatches() - { - // Arrange - var expected = new[] - { - new MyClass2 - { - One = new StringContainer("EXPECTED", "EXPECTED"), - Two = new StringContainer("CORRECT") - }, - new MyClass2() - }; - - var actual = new[] - { - new MyClass2 - { - One = new StringContainer("INCORRECT", "INCORRECT"), - Two = new StringContainer("CORRECT") - }, - new MyClass2() - }; - - // Act - Action act = () => actual.Should().BeEquivalentTo(expected); - - // Assert - act.Should().Throw() - .WithMessage("*EXPECTED*INCORRECT*EXPECTED*INCORRECT*"); - } - - [Fact] - public void When_the_nested_object_property_is_null_it_should_throw() - { - // Arrange - var subject = new Root - { - Text = "Root", - Level = null - }; - - var expected = new RootDto - { - Text = "Root", - Level = new Level1Dto - { - Text = "Level2" - } - }; - - // Act - Action act = () => - subject.Should().BeEquivalentTo(expected); - - // Assert - act - .Should().Throw() - .WithMessage("Expected property subject.Level*to be*Level1Dto*Level2*, but found *"); - } - - [Fact] - public void When_not_all_the_properties_of_the_nested_object_exist_on_the_expected_object_it_should_throw() - { - // Arrange - var subject = new - { - Level = new - { - Text = "Level1", - } - }; - - var expected = new - { - Level = new - { - Text = "Level1", - OtherProperty = "OtherProperty" - } - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected); - - // Assert - act - .Should().Throw() - .WithMessage("Expectation has property subject.Level.OtherProperty that the other object does not have*"); - } - - [Fact] - public void When_all_the_shared_properties_of_the_nested_objects_are_equal_it_should_succeed() - { - // Arrange - var subject = new - { - Level = new - { - Text = "Level1", - Property = "Property" - } - }; - - var expected = new - { - Level = new - { - Text = "Level1", - OtherProperty = "OtherProperty" - } - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected, options => options.ExcludingMissingMembers()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_deeply_nested_properties_do_not_have_all_equal_values_it_should_throw() - { - // Arrange - var root = new Root - { - Text = "Root", - Level = new Level1 - { - Text = "Level1", - Level = new Level2 - { - Text = "Level2" - } - } - }; - - var rootDto = new RootDto - { - Text = "Root", - Level = new Level1Dto - { - Text = "Level1", - Level = new Level2Dto - { - Text = "A wrong text value" - } - } - }; - - // Act - Action act = () => root.Should().BeEquivalentTo(rootDto); - - // Assert - act - .Should().Throw() - .WithMessage( - "Expected*Level.Level.Text*to be *A wrong text value*but*\"Level2\"*length*"); - } - - [Fact] - public void When_two_objects_have_the_same_nested_objects_it_should_not_throw() - { - // Arrange - var c1 = new ClassOne(); - var c2 = new ClassOne(); - - // Act - Action act = () => c1.Should().BeEquivalentTo(c2); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_property_of_a_nested_object_doesnt_match_it_should_clearly_indicate_the_path() - { - // Arrange - var c1 = new ClassOne(); - var c2 = new ClassOne(); - c2.RefOne.ValTwo = 2; - - // Act - Action act = () => c1.Should().BeEquivalentTo(c2); - - // Assert - act.Should().Throw() - .WithMessage("Expected property c1.RefOne.ValTwo to be 2, but found 3*"); - } - - [Fact] - public void Should_support_nested_collections_containing_empty_objects() - { - // Arrange - var orig = new[] - { - new OuterWithObject - { - MyProperty = new[] { new Inner() } - } - }; - - var expectation = new[] - { - new OuterWithObject - { - MyProperty = new[] { new Inner() } - } - }; - - // Act / Assert - orig.Should().BeEquivalentTo(expectation); - } - - public class Inner { } - - public class OuterWithObject - { - public Inner[] MyProperty { get; set; } - } - - #endregion - - public class MemberMapping - { - [Fact] - public void Nested_properties_can_be_mapped_using_a_nested_expression() - { - // Arrange - var subject = new ParentOfSubjectWithProperty1(new[] - { - new SubjectWithProperty1 - { - Property1 = "Hello" - } - }); - - var expectation = new ParentOfExpectationWithProperty2(new[] - { - new ExpectationWithProperty2 - { - Property2 = "Hello" - } - }); - - // Act / Assert - subject.Should() - .BeEquivalentTo(expectation, opt => opt - .WithMapping( - e => e.Parent[0].Property2, - s => s.Parent[0].Property1)); - } - - [Fact] - 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 expectation = new ParentOfExpectationWithProperty2(new[] - { - new ExpectationWithProperty2 - { - Property2 = "Hello" - } - }); - - // Act / Assert - subject.Should() - .BeEquivalentTo(expectation, opt => opt - .WithMapping("Property2", "Property1")); - } - - [Fact] - public void Nested_fields_can_be_mapped_using_a_nested_type_and_field_names() - { - // Arrange - var subject = new ClassWithSomeFieldsAndProperties - { - Field1 = "John", - Field2 = "Mary" - }; - - var expectation = new ClassWithSomeFieldsAndProperties - { - Field1 = "Mary", - Field2 = "John" - }; - - // Act / Assert - subject.Should() - .BeEquivalentTo(expectation, opt => opt - .WithMapping("Field1", "Field2") - .WithMapping("Field2", "Field1")); - } - - [Fact] - 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 expectation = new ParentOfExpectationWithProperty2(new[] - { - new ExpectationWithProperty2 - { - Property2 = "Hello" - } - }); - - // Act / Assert - subject.Should() - .BeEquivalentTo(expectation, opt => opt - .WithMapping( - e => e.Property2, s => s.Property1)); - } - - [Fact] - public void Nested_properties_on_a_collection_can_be_mapped_using_a_dotted_path() - { - // Arrange - var subject = new - { - Parent = new[] - { - new SubjectWithProperty1 - { - Property1 = "Hello" - } - } - }; - - var expectation = new - { - Parent = new[] - { - new ExpectationWithProperty2 - { - Property2 = "Hello" - } - } - }; - - // Act / Assert - subject.Should() - .BeEquivalentTo(expectation, opt => opt - .WithMapping("Parent[].Property2", "Parent[].Property1")); - } - - [Fact] - public void Properties_can_be_mapped_by_name() - { - // Arrange - var subject = new SubjectWithProperty1 - { - Property1 = "Hello" - }; - - var expectation = new ExpectationWithProperty2 - { - Property2 = "Hello" - }; - - // Act / Assert - subject.Should() - .BeEquivalentTo(expectation, opt => opt - .WithMapping("Property2", "Property1")); - } - - [Fact] - public void Fields_can_be_mapped_by_name() - { - // Arrange - var subject = new ClassWithSomeFieldsAndProperties - { - Field1 = "Hello", - Field2 = "John" - }; - - var expectation = new ClassWithSomeFieldsAndProperties - { - Field1 = "John", - Field2 = "Hello" - }; - - // Act / Assert - subject.Should() - .BeEquivalentTo(expectation, opt => opt - .WithMapping("Field2", "Field1") - .WithMapping("Field1", "Field2")); - } - - [Fact] - public void Fields_can_be_mapped_to_a_property_by_name() - { - // Arrange - var subject = new ClassWithSomeFieldsAndProperties - { - Property1 = "John" - }; - - var expectation = new ClassWithSomeFieldsAndProperties - { - Field1 = "John", - }; - - // Act / Assert - subject.Should() - .BeEquivalentTo(expectation, opt => opt - .WithMapping("Field1", "Property1") - .Including(e => e.Field1)); - } - - [Fact] - public void Properties_can_be_mapped_to_a_field_by_expression() - { - // Arrange - var subject = new ClassWithSomeFieldsAndProperties - { - Field1 = "John", - }; - - var expectation = new ClassWithSomeFieldsAndProperties - { - Property1 = "John" - }; - - // Act / Assert - subject.Should() - .BeEquivalentTo(expectation, opt => opt - .WithMapping(e => e.Property1, s => s.Field1) - .Including(e => e.Property1)); - } - - [Fact] - public void Properties_can_be_mapped_to_inherited_properties() - { - // Arrange - var subject = new Derived - { - BaseProperty = "Hello World" - }; - - var expectation = new - { - AnotherProperty = "Hello World" - }; - - // Act / Assert - subject.Should() - .BeEquivalentTo(expectation, opt => opt - .WithMapping(e => e.AnotherProperty, s => s.BaseProperty)); - } - - [Fact] - public void A_failed_assertion_reports_the_subjects_mapped_property() - { - // Arrange - var subject = new SubjectWithProperty1 - { - Property1 = "Hello" - }; - - var expectation = new ExpectationWithProperty2 - { - Property2 = "Hello2" - }; - - // Act - Action act = () => subject.Should() - .BeEquivalentTo(expectation, opt => opt - .WithMapping(e => e.Property2, e => e.Property1)); - - // Assert - act.Should() - .Throw() - .WithMessage("Expected property subject.Property1 to be*Hello*"); - } - - [Fact] - public void An_empty_expectation_member_path_is_not_allowed() - { - var subject = new SubjectWithProperty1(); - var expectation = new ExpectationWithProperty2(); - - // Act - Action act = () => subject.Should() - .BeEquivalentTo(expectation, opt => opt - .WithMapping("", "Parent[0].Property1")); - - // Assert - act.Should() - .Throw() - .WithMessage("*member path*"); - } - - [Fact] - public void An_empty_subject_member_path_is_not_allowed() - { - var subject = new SubjectWithProperty1(); - var expectation = new ExpectationWithProperty2(); - - // Act - Action act = () => subject.Should() - .BeEquivalentTo(expectation, opt => opt - .WithMapping("Parent[0].Property1", "")); - - // Assert - act.Should() - .Throw() - .WithMessage("*member path*"); - } - - [Fact] - public void Null_as_the_expectation_member_path_is_not_allowed() - { - var subject = new SubjectWithProperty1(); - var expectation = new ExpectationWithProperty2(); - - // Act - Action act = () => subject.Should() - .BeEquivalentTo(expectation, opt => opt - .WithMapping(null, "Parent[0].Property1")); - - // Assert - act.Should() - .Throw() - .WithMessage("*member path*"); - } - - [Fact] - public void Null_as_the_subject_member_path_is_not_allowed() - { - var subject = new SubjectWithProperty1(); - var expectation = new ExpectationWithProperty2(); - - // Act - Action act = () => subject.Should() - .BeEquivalentTo(expectation, opt => opt - .WithMapping("Parent[0].Property1", null)); - - // Assert - act.Should() - .Throw() - .WithMessage("*member path*"); - } - - [Fact] - public void Subject_and_expectation_member_paths_must_have_the_same_parent() - { - var subject = new SubjectWithProperty1(); - var expectation = new ExpectationWithProperty2(); - - // Act - Action act = () => subject.Should() - .BeEquivalentTo(expectation, opt => opt - .WithMapping("Parent[].Property1", "OtherParent[].Property2")); - - // Assert - act.Should() - .Throw() - .WithMessage("*parent*"); - } - - [Fact] - public void Numeric_indexes_in_the_path_are_not_allowed() - { - var subject = new - { - Parent = new[] - { - new SubjectWithProperty1 - { - Property1 = "Hello" - } - } - }; - - var expectation = new - { - Parent = new[] - { - new ExpectationWithProperty2 - { - Property2 = "Hello" - } - } - }; - - // Act - Action act = () => subject.Should() - .BeEquivalentTo(expectation, opt => opt - .WithMapping("Parent[0].Property2", "Parent[0].Property1")); - - // Assert - act.Should() - .Throw() - .WithMessage("*without specific index*"); - } - - [Fact] - public void Mapping_to_a_non_existing_subject_member_is_not_allowed() - { - // Arrange - var subject = new SubjectWithProperty1 - { - Property1 = "Hello" - }; - - var expectation = new ExpectationWithProperty2 - { - Property2 = "Hello" - }; - - // Act - Action act = () => subject.Should() - .BeEquivalentTo(expectation, opt => opt - .WithMapping("Property2", "NonExistingProperty")); - - // Assert - act.Should() - .Throw() - .WithMessage("*not have member NonExistingProperty*"); - } - - [Fact] - public void A_null_subject_should_result_in_a_normal_assertion_failure() - { - // Arrange - SubjectWithProperty1 subject = null; - - ExpectationWithProperty2 expectation = new() - { - Property2 = "Hello" - }; - - // Act - Action act = () => subject.Should() - .BeEquivalentTo(expectation, opt => opt - .WithMapping("Property2", "Property1")); - - // Assert - act.Should() - .Throw() - .WithMessage("*Expected*ExpectationWithProperty2*found *"); - } - - [Fact] - public void Nested_types_and_dotted_expectation_member_paths_cannot_be_combined() - { - // Arrange - var subject = new ParentOfSubjectWithProperty1(new[] - { - new SubjectWithProperty1 - { - Property1 = "Hello" - } - }); - - var expectation = new ParentOfExpectationWithProperty2(new[] - { - new ExpectationWithProperty2 - { - Property2 = "Hello" - } - }); - - // Act - Action act = () => subject.Should() - .BeEquivalentTo(expectation, opt => opt - .WithMapping("Parent.Property2", "Property1")); - - // Assert - act.Should() - .Throw() - .WithMessage("*cannot be a nested path*"); - } - - [Fact] - public void Nested_types_and_dotted_subject_member_paths_cannot_be_combined() - { - // Arrange - var subject = new ParentOfSubjectWithProperty1(new[] - { - new SubjectWithProperty1 - { - Property1 = "Hello" - } - }); - - var expectation = new ParentOfExpectationWithProperty2(new[] - { - new ExpectationWithProperty2 - { - Property2 = "Hello" - } - }); - - // Act - Action act = () => subject.Should() - .BeEquivalentTo(expectation, opt => opt - .WithMapping("Property2", "Parent.Property1")); - - // Assert - act.Should() - .Throw() - .WithMessage("*cannot be a nested path*"); - } - - [Fact] - 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 expectation = new ParentOfExpectationWithProperty2(new[] - { - new ExpectationWithProperty2 - { - Property2 = "Hello" - } - }); - - // Act - Action act = () => subject.Should() - .BeEquivalentTo(expectation, opt => opt - .WithMapping("Property2", "NonExistingProperty")); - - // Assert - act.Should() - .Throw() - .WithMessage("*does not have member NonExistingProperty*"); - } - - internal class ParentOfExpectationWithProperty2 - { - public ExpectationWithProperty2[] Parent { get; } - - public ParentOfExpectationWithProperty2(ExpectationWithProperty2[] parent) - { - Parent = parent; - } - } - - internal class ParentOfSubjectWithProperty1 - { - public SubjectWithProperty1[] Parent { get; } - - public ParentOfSubjectWithProperty1(SubjectWithProperty1[] parent) - { - Parent = parent; - } - } - - internal class SubjectWithProperty1 - { - public string Property1 { get; set; } - } - - internal class ExpectationWithProperty2 - { - public string Property2 { get; set; } - } - - internal class Base - { - public string BaseProperty { get; set; } - } - - internal class Derived : Base - { - public string DerivedProperty { get; set; } - } - } - - #region Cyclic References - - [Fact] - public void When_validating_nested_properties_that_have_cyclic_references_it_should_throw() - { - // Arrange - var cyclicRoot = new CyclicRoot - { - Text = "Root" - }; - - cyclicRoot.Level = new CyclicLevel1 - { - Text = "Level1", - Root = cyclicRoot - }; - - var cyclicRootDto = new CyclicRootDto - { - Text = "Root" - }; - - cyclicRootDto.Level = new CyclicLevel1Dto - { - Text = "Level1", - Root = cyclicRootDto - }; - - // Act - Action act = () => cyclicRoot.Should().BeEquivalentTo(cyclicRootDto); - - // Assert - act - .Should().Throw() - .WithMessage("Expected property cyclicRoot.Level.Root to be*but it contains a cyclic reference*"); - } - - [Fact] - public void When_validating_nested_properties_and_ignoring_cyclic_references_it_should_succeed() - { - // Arrange - var cyclicRoot = new CyclicRoot - { - Text = "Root" - }; - cyclicRoot.Level = new CyclicLevel1 - { - Text = "Level1", - Root = cyclicRoot - }; - - var cyclicRootDto = new CyclicRootDto - { - Text = "Root" - }; - cyclicRootDto.Level = new CyclicLevel1Dto - { - Text = "Level1", - Root = cyclicRootDto - }; - - // Act - Action act = () => cyclicRoot.Should().BeEquivalentTo(cyclicRootDto, options => options.IgnoringCyclicReferences()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_two_cyclic_graphs_are_equivalent_when_ignoring_cycle_references_it_should_succeed() - { - // Arrange - var actual = new Parent(); - actual.Child1 = new Child(actual, 1); - actual.Child2 = new Child(actual); - - var expected = new Parent(); - expected.Child1 = new Child(expected); - expected.Child2 = new Child(expected); - - // Act - Action act = () => actual.Should().BeEquivalentTo(expected, x => x - .Excluding(y => y.Child1) - .IgnoringCyclicReferences()); - - // Assert - act.Should().NotThrow(); - } - - public class Parent - { - public Child Child1 { get; set; } - - public Child Child2 { get; set; } - } - - public class Child - { - public Child(Parent parent, int stuff = 0) - { - Parent = parent; - Stuff = stuff; - } - - public Parent Parent { get; set; } - - public int Stuff { get; set; } - } - - [Fact] - public void When_validating_nested_properties_that_are_null_it_should_not_throw_on_cyclic_references() - { - // Arrange - var actual = new CyclicRoot - { - Text = null - }; - - actual.Level = new CyclicLevel1 - { - Text = null, - Root = null - }; - - var expectation = new CyclicRootDto - { - Text = null - }; - - expectation.Level = new CyclicLevel1Dto - { - Text = null, - Root = null - }; - - // Act - Action act = () => actual.Should().BeEquivalentTo(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_the_graph_contains_the_same_value_object_it_should_not_be_treated_as_a_cyclic_reference() - { - // Arrange - var actual = new CyclicRootWithValueObject - { - Object = new ValueObject("MyValue") - }; - - actual.Level = new CyclicLevelWithValueObject - { - Object = new ValueObject("MyValue"), - Root = null - }; - - var expectation = new CyclicRootWithValueObject - { - Object = new ValueObject("MyValue") - }; - - expectation.Level = new CyclicLevelWithValueObject - { - Object = new ValueObject("MyValue"), - Root = null - }; - - // Act - Action act = () => actual.Should().BeEquivalentTo(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_types_with_infinite_object_graphs_are_equivalent_it_should_not_overflow_the_stack() - { - // Arrange - var recursiveClass1 = new ClassWithInfinitelyRecursiveProperty(); - var recursiveClass2 = new ClassWithInfinitelyRecursiveProperty(); - - // Act - Action act = - () => recursiveClass1.Should().BeEquivalentTo(recursiveClass2); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_equivalence_on_objects_needing_high_recursion_depth_and_disabling_recursion_depth_limit_it_should_recurse_to_completion() - { - // Arrange - var recursiveClass1 = new ClassWithFiniteRecursiveProperty(15); - var recursiveClass2 = new ClassWithFiniteRecursiveProperty(15); - - // Act - Action act = - () => recursiveClass1.Should().BeEquivalentTo(recursiveClass2, - options => options.AllowingInfiniteRecursion()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_injecting_a_null_config_to_BeEquivalentTo_it_should_throw() - { - // Arrange - var recursiveClass1 = new ClassWithFiniteRecursiveProperty(15); - var recursiveClass2 = new ClassWithFiniteRecursiveProperty(15); - - // Act - Action act = () => recursiveClass1.Should().BeEquivalentTo(recursiveClass2, config: null); - - // Assert - act.Should().ThrowExactly() - .WithParameterName("config"); - } - - [Fact] - public void When_asserting_inequivalence_on_objects_needing_high_recursion_depth_and_disabling_recursion_depth_limit_it_should_recurse_to_completion() - { - // Arrange - var recursiveClass1 = new ClassWithFiniteRecursiveProperty(15); - var recursiveClass2 = new ClassWithFiniteRecursiveProperty(16); - - // Act - Action act = - () => recursiveClass1.Should().NotBeEquivalentTo(recursiveClass2, - options => options.AllowingInfiniteRecursion()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_injecting_a_null_config_to_NotBeEquivalentTo_it_should_throw() - { - // Arrange - var recursiveClass1 = new ClassWithFiniteRecursiveProperty(15); - var recursiveClass2 = new ClassWithFiniteRecursiveProperty(16); - - // Act - Action act = () => recursiveClass1.Should().NotBeEquivalentTo(recursiveClass2, config: null); - - // Assert - act.Should().ThrowExactly() - .WithParameterName("config"); - } - - [Fact] - public void When_an_enumerable_collection_returns_itself_it_should_detect_the_cyclic_reference() - { - // Act - var instance1 = new SelfReturningEnumerable(); - var instance2 = new SelfReturningEnumerable(); - var actual = new List { instance1, instance2 }; - - // Assert - Action act = () => actual.Should().BeEquivalentTo( - new[] { new SelfReturningEnumerable(), new SelfReturningEnumerable() }, - "we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw().WithMessage("*we want to test the failure message*cyclic reference*"); - } - - public class SelfReturningEnumerable : IEnumerable - { - public IEnumerator GetEnumerator() - { - yield return this; - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } - - internal class LogbookEntryProjection - { - public virtual LogbookCode Logbook { get; set; } - - public virtual ICollection LogbookRelations { get; set; } - } - - internal class LogbookRelation - { - public virtual LogbookCode Logbook { get; set; } - } - - internal class LogbookCode - { - public LogbookCode(string key) - { - Key = key; - } - - public string Key { get; protected set; } - } - - [Fact] - public void When_the_root_object_is_referenced_from_a_nested_object_it_should_treat_it_as_a_cyclic_reference() - { - // Arrange - var company1 = new MyCompany { Name = "Company" }; - var user1 = new MyUser { Name = "User", Company = company1 }; - 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 }; - var logo2 = new MyCompanyLogo { Url = "blank", Company = company2, CreatedBy = user2 }; - company2.Logo = logo2; - - // Act - Action action = () => company1.Should().BeEquivalentTo(company2, o => o.IgnoringCyclicReferences()); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void Allow_ignoring_cyclic_references_in_value_types_compared_by_members() - { - // Arrange - var expectation = new ValueTypeCircularDependency() - { - Title = "First" - }; - - var second = new ValueTypeCircularDependency() - { - Title = "Second", - Previous = expectation - }; - - expectation.Next = second; - - var subject = new ValueTypeCircularDependency() - { - Title = "First" - }; - - var secondCopy = new ValueTypeCircularDependency() - { - Title = "SecondDifferent", - Previous = subject - }; - - subject.Next = secondCopy; - - // Act - Action act = () => subject.Should() - .BeEquivalentTo(expectation, opt => opt - .ComparingByMembers() - .IgnoringCyclicReferences()); - - // Assert - act.Should().Throw() - .WithMessage("*subject.Next.Title*Second*SecondDifferent*") - .Which.Message.Should().NotContain("maximum recursion depth was reached"); - } - - public class ValueTypeCircularDependency - { - public string Title { get; set; } - - public ValueTypeCircularDependency Previous { get; set; } - - public ValueTypeCircularDependency Next { get; set; } - - public override bool Equals(object obj) - { - if (ReferenceEquals(this, obj)) - { - return true; - } - - return (obj is ValueTypeCircularDependency baseObj) && baseObj.Title == Title; - } - - public override int GetHashCode() - { - return Title.GetHashCode(); - } - } - - #endregion - - #region Tuples - - [Fact] - public void When_a_nested_member_is_a_tuple_it_should_compare_its_property_for_equivalence() - { - // Arrange - var actual = new - { - Tuple = (new[] { "string1" }, new[] { "string2" }) - }; - - var expected = new - { - Tuple = (new[] { "string1" }, new[] { "string2" }) - }; - - // Act - Action act = () => actual.Should().BeEquivalentTo(expected); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_tuple_is_compared_it_should_compare_its_components() - { - // Arrange - var actual = new Tuple("Hello", true, new int[] { 3, 2, 1 }); - var expected = new Tuple("Hello", true, new int[] { 1, 2, 3 }); - - // Act - Action act = () => actual.Should().BeEquivalentTo(expected); - - // Assert - act.Should().NotThrow(); - } - - #endregion - - #region Records - - [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 expected = new MyRecord - { - StringField = "foo", - CollectionProperty = new[] { "foo", "bar", "zip" } - }; - - actual.Should().BeEquivalentTo(expected); - } - - [Fact] - public void When_the_subject_is_a_record_it_should_mention_that_in_the_configuration_output() - { - var actual = new MyRecord - { - StringField = "foo", - }; - - var expected = new MyRecord - { - StringField = "bar", - }; - - Action act = () => actual.Should().BeEquivalentTo(expected); - - act.Should().Throw() - .WithMessage("*Compare records by their members*"); - } - - [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 expected = new MyRecord - { - StringField = "foo", - CollectionProperty = new[] { "foo", "bar", "zip" } - }; - - Action act = () => actual.Should().BeEquivalentTo(expected, o => o - .ComparingByValue()); - - act.Should().Throw() - .WithMessage("*Expected*MyRecord*but found*MyRecord*") - .WithMessage("*Compare*MyRecord by value*"); - } - - [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 expected = new MyRecord - { - StringField = "foo", - CollectionProperty = new[] { "foo", "bar", "zip" } - }; - - Action act = () => actual.Should().BeEquivalentTo(expected, o => o - .ComparingRecordsByValue()); - - act.Should().Throw() - .WithMessage("*Expected*MyRecord*but found*MyRecord*") - .WithMessage("*Compare records by value*"); - } - - [Fact] - 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 expected = new MyRecord - { - StringField = "foo", - CollectionProperty = new[] { "foo", "bar", "zip" } - }; - - actual.Should().BeEquivalentTo(expected, o => o - .ComparingRecordsByValue() - .ComparingByMembers()); - } - - [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 expected = new MyRecord - { - CollectionProperty = new[] { "foo", "bar", "zip" } - }; - - actual.Should().BeEquivalentTo(expected, o => o - .ComparingRecordsByValue() - .ComparingRecordsByMembers()); - } - - private record MyRecord - { - public string StringField; - - public string[] CollectionProperty { get; init; } - } - - #endregion - - #region Enums - - [Fact] - public void When_asserting_the_same_enum_member_is_equivalent_it_should_succeed() - { - // Arrange - object subject = EnumOne.One; - object expectation = EnumOne.One; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_the_actual_enum_value_is_null_it_should_report_that_properly() - { - // Arrange - var subject = new - { - NullableEnum = (DayOfWeek?)null - }; - - var expectation = new - { - NullableEnum = DayOfWeek.Friday - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected*5*null*"); - } - - [Fact] - public void When_the_actual_enum_name_is_null_it_should_report_that_properly() - { - // Arrange - var subject = new - { - NullableEnum = (DayOfWeek?)null - }; - - var expectation = new - { - NullableEnum = DayOfWeek.Friday - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation, o => o.ComparingEnumsByValue()); - - // Assert - act.Should().Throw() - .WithMessage("Expected*5*null*"); - } - - [Fact] - public void When_asserting_different_enum_members_are_equivalent_it_should_fail() - { - // Arrange - object subject = EnumOne.One; - object expectation = EnumOne.Two; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected *EnumOne.Two {value: 3}*but*EnumOne.One {value: 0}*"); - } - - [Fact] - public void When_asserting_members_from_different_enum_types_are_equivalent_it_should_compare_by_value_by_default() - { - // Arrange - var subject = new ClassWithEnumOne(); - var expectation = new ClassWithEnumTwo(); - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_members_from_different_enum_types_are_equivalent_by_value_it_should_succeed() - { - // Arrange - var subject = new ClassWithEnumOne { Enum = EnumOne.One }; - var expectation = new ClassWithEnumThree { Enum = EnumThree.ValueZero }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation, config => config.ComparingEnumsByValue()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_members_from_different_enum_types_are_equivalent_by_string_value_it_should_succeed() - { - // Arrange - var subject = new ClassWithEnumOne { Enum = EnumOne.Two }; - var expectation = new ClassWithEnumThree() { Enum = EnumThree.Two }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation, config => config.ComparingEnumsByName()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_members_from_different_enum_types_are_equivalent_by_value_but_comparing_by_name_it_should_throw() - { - // Arrange - var subject = new ClassWithEnumOne { Enum = EnumOne.Two }; - var expectation = new ClassWithEnumFour { Enum = EnumFour.Three }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation, config => config.ComparingEnumsByName()); - - // Assert - act.Should().Throw().WithMessage("Expected*to equal EnumFour.Three {value: 3} by name, but found EnumOne.Two {value: 3}*"); - } - - [Fact] - public void When_asserting_members_from_different_char_enum_types_are_equivalent_by_value_it_should_succeed() - { - // Arrange - var subject = new ClassWithEnumCharOne { Enum = EnumCharOne.B }; - var expectation = new ClassWithEnumCharTwo { Enum = EnumCharTwo.ValueB }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation, config => config.ComparingEnumsByValue()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_enums_typed_as_object_are_equivalent_it_should_succeed() - { - // Arrange - object e1 = EnumOne.One; - object e2 = EnumOne.One; - - // Act - Action act = () => e1.Should().BeEquivalentTo(e2); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_numeric_member_is_compared_with_an_enum_it_should_throw() - { - // Arrange - var actual = new - { - Property = 1 - }; - - var expected = new - { - Property = TestEnum.First - }; - - // Act - Action act = () => actual.Should().BeEquivalentTo(expected, options => options.ComparingEnumsByValue()); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_string_member_is_compared_with_an_enum_it_should_throw() - { - // Arrange - var actual = new - { - Property = "First" - }; - - var expected = new - { - Property = TestEnum.First - }; - - // Act - Action act = () => actual.Should().BeEquivalentTo(expected, options => options.ComparingEnumsByName()); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_null_enum_members_are_compared_by_name_it_should_succeed() - { - // Arrange - var actual = new - { - Property = null as TestEnum? - }; - - var expected = new - { - Property = null as TestEnum? - }; - - // Act - Action act = () => actual.Should().BeEquivalentTo(expected, options => options.ComparingEnumsByName()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_null_enum_members_are_compared_by_value_it_should_succeed() - { - // Arrange - var actual = new - { - Property = null as TestEnum? - }; - - var expected = new - { - Property = null as TestEnum? - }; - - // Act - Action act = () => actual.Should().BeEquivalentTo(expected, options => options.ComparingEnumsByValue()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_zero_and_null_enum_are_compared_by_value_it_should_throw() - { - // Arrange - var actual = new - { - Property = (TestEnum)0 - }; - - var expected = new - { - Property = null as TestEnum? - }; - - // Act - Action act = () => actual.Should().BeEquivalentTo(expected, options => options.ComparingEnumsByValue()); - - // Assert - act.Should().Throw(); - } - - public enum TestEnum - { - First = 1 - } - - [Fact] - public void When_subject_is_null_and_enum_has_some_value_it_should_throw() - { - // Arrange - object subject = null; - object expectedEnum = EnumULong.UInt64Max; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectedEnum, x => x.ComparingEnumsByName(), "comparing enums should throw"); - - // Assert - act.Should().Throw() - .WithMessage("Expected*to be equivalent to EnumULong.UInt64Max {value: 18446744073709551615} because comparing enums should throw, but found *"); - } - - [Fact] - public void When_expectation_is_null_and_subject_enum_has_some_value_it_should_throw_with_a_useful_message() - { - // Arrange - object subjectEnum = EnumULong.UInt64Max; - object expected = null; - - // Act - Action act = () => subjectEnum.Should().BeEquivalentTo(expected, x => x.ComparingEnumsByName(), "comparing enums should throw"); - - // Assert - act.Should().Throw() - .WithMessage("Expected*to be because comparing enums should throw, but found EnumULong.UInt64Max*"); - } - - [Fact] - public void When_both_enums_are_equal_and_greater_than_max_long_it_should_not_throw() - { - // Arrange - object enumOne = EnumULong.UInt64Max; - object enumTwo = EnumULong.UInt64Max; - - // Act - Action act = () => enumOne.Should().BeEquivalentTo(enumTwo); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_both_enums_are_equal_and_of_different_underlying_types_it_should_not_throw() - { - // Arrange - object enumOne = EnumLong.Int64Max; - object enumTwo = EnumULong.Int64Max; - - // Act - Action act = () => enumOne.Should().BeEquivalentTo(enumTwo); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_both_enums_are_large_and_not_equal_it_should_throw() - { - // Arrange - object subjectEnum = EnumLong.Int64LessOne; - object expectedEnum = EnumULong.UInt64Max; - - // Act - Action act = () => subjectEnum.Should().BeEquivalentTo(expectedEnum, "comparing enums should throw"); - - // Assert - act.Should().Throw() - .WithMessage("Expected subjectEnum*to equal EnumULong.UInt64Max {value: 18446744073709551615} by value because comparing enums should throw, but found EnumLong.Int64LessOne {value: 9223372036854775806}*"); - } - - #endregion - - #region Memberless Objects - - [Fact] - public void When_asserting_instances_of_an_anonymous_type_having_no_members_are_equivalent_it_should_fail() - { - // Arrange / Act - Action act = () => new { }.Should().BeEquivalentTo(new { }); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_asserting_instances_of_a_class_having_no_members_are_equivalent_it_should_fail() - { - // Arrange / Act - Action act = () => new ClassWithNoMembers().Should().BeEquivalentTo(new ClassWithNoMembers()); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_asserting_instances_of_Object_are_equivalent_it_should_fail() - { - // Arrange / Act - Action act = () => new object().Should().BeEquivalentTo(new object()); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_asserting_instance_of_object_is_equivalent_to_null_it_should_fail_with_a_descriptive_message() - { - // Arrange - object actual = new object(); - object expected = null; - - // Act - Action act = () => actual.Should().BeEquivalentTo(expected, "we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw() - .WithMessage("*Expected*to be *we want to test the failure message*, but found System.Object*"); - } - - [Fact] - public void When_asserting_null_is_equivalent_to_instance_of_object_it_should_fail() - { - // Arrange - object actual = null; - object expected = new object(); - - // Act - Action act = () => actual.Should().BeEquivalentTo(expected); - - // Assert - act.Should().Throw() - .WithMessage("*Expected*to be System.Object*but found *"); - } - - [Fact] - public void When_an_type_only_exposes_fields_but_fields_are_ignored_in_the_equivalence_comparision_it_should_fail() - { - // Arrange - var object1 = new ClassWithOnlyAField { Value = 1 }; - var object2 = new ClassWithOnlyAField { Value = 101 }; - - // Act - Action act = () => object1.Should().BeEquivalentTo(object2, opts => opts.IncludingAllDeclaredProperties()); - - // Assert - act.Should().Throw("the objects have no members to compare."); - } - - [Fact] - public void When_an_type_only_exposes_properties_but_properties_are_ignored_in_the_equivalence_comparision_it_should_fail() - { - // Arrange - var object1 = new ClassWithOnlyAProperty { Value = 1 }; - var object2 = new ClassWithOnlyAProperty { Value = 101 }; - - // Act - Action act = () => object1.Should().BeEquivalentTo(object2, opts => opts.ExcludingProperties()); - - // Assert - act.Should().Throw("the objects have no members to compare."); - } - - [Fact] - public void When_asserting_instances_of_arrays_of_types_in_System_are_equivalent_it_should_respect_the_runtime_type() - { - // Arrange - object actual = new int[0]; - object expectation = new int[0]; - - // Act - Action act = () => actual.Should().BeEquivalentTo(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_throwing_on_missing_members_and_there_are_no_missing_members_should_not_throw() - { - // Arrange - var subject = new - { - Version = 2, - Age = 36, - }; - - var expectation = new - { - Version = 2, - Age = 36 - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation, - options => options.ThrowingOnMissingMembers()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_throwing_on_missing_members_and_there_is_a_missing_member_should_throw() - { - // Arrange - var subject = new - { - Version = 2 - }; - - var expectation = new - { - Version = 2, - Age = 36 - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation, - options => options.ThrowingOnMissingMembers()); - - // Assert - act.Should().Throw() - .WithMessage("Expectation has property subject.Age that the other object does not have*"); - } - - [Fact] - public void When_throwing_on_missing_members_and_there_is_an_additional_property_on_subject_should_not_throw() - { - // Arrange - var subject = new - { - Version = 2, - Age = 36, - Additional = 13 - }; - - var expectation = new - { - Version = 2, - Age = 36 - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation, - options => options.ThrowingOnMissingMembers()); - - // Assert - act.Should().NotThrow(); - } - - #endregion - } - - #region Test Classes - - public class ClassOne - { - public ClassTwo RefOne { get; set; } = new ClassTwo(); - - public int ValOne { get; set; } = 1; - } - - public class ClassTwo - { - public int ValTwo { get; set; } = 3; - } - - public class ClassWithWriteOnlyProperty - { - private int writeOnlyPropertyValue; - - public int WriteOnlyProperty - { - set { writeOnlyPropertyValue = value; } - } - - public string SomeOtherProperty { get; set; } - } - - internal enum EnumOne - { - One = 0, - Two = 3 - } - - internal enum EnumCharOne - { - A = 'A', - B = 'B' - } - - internal enum EnumCharTwo - { - A = 'Z', - ValueB = 'B' - } - - internal enum EnumTwo - { - One = 0, - Two = 3 - } - - internal enum EnumThree - { - ValueZero = 0, - Two = 3 - } - - internal enum EnumFour - { - Three = 3 - } - - internal class ClassWithEnumCharOne - { - public EnumCharOne Enum { get; set; } - } - - internal class ClassWithEnumCharTwo - { - public EnumCharTwo Enum { get; set; } - } - - internal class ClassWithEnumOne - { - public EnumOne Enum { get; set; } - } - - internal class ClassWithEnumTwo - { - public EnumTwo Enum { get; set; } - } - - internal class ClassWithEnumThree - { - public EnumThree Enum { get; set; } - } - - internal class ClassWithEnumFour - { - public EnumFour Enum { get; set; } - } - - internal class ClassWithNoMembers - { - } - - internal class ClassWithOnlyAField - { - public int Value; - } - - internal class ClassWithAPrivateField : ClassWithOnlyAField - { - private readonly int value; - - public ClassWithAPrivateField(int value) - { - this.value = value; - } - } - - internal class ClassWithOnlyAProperty - { - public int Value { get; set; } - } - - internal struct StructWithNoMembers - { - } - - internal class ClassWithInfinitelyRecursiveProperty - { - public ClassWithInfinitelyRecursiveProperty Self - { - get { return new ClassWithInfinitelyRecursiveProperty(); } - } - } - - internal class ClassWithFiniteRecursiveProperty - { - private readonly int depth; - - public ClassWithFiniteRecursiveProperty(int recursiveDepth) - { - depth = recursiveDepth; - } - - public ClassWithFiniteRecursiveProperty Self - { - get - { - return depth > 0 - ? new ClassWithFiniteRecursiveProperty(depth - 1) - : null; - } - } - } - - internal class ClassWithSomeFieldsAndProperties - { - public string Field1; - - public string Field2; - - public string Field3; - - public string Property1 { get; set; } - - public string Property2 { get; set; } - - public string Property3 { get; set; } - } - - internal class ClassWithCctor - { - static ClassWithCctor() { } - } - - internal class ClassWithCctorAndNonDefaultConstructor - { - static ClassWithCctorAndNonDefaultConstructor() { } - - public ClassWithCctorAndNonDefaultConstructor(int _) { } - } - - internal class MyCompanyLogo - { - public string Url { get; set; } - - public MyCompany Company { get; set; } - - public MyUser CreatedBy { get; set; } - } - - internal class MyUser - { - public string Name { get; set; } - - public MyCompany Company { get; set; } - } - - internal class MyCompany - { - public string Name { get; set; } - - public MyCompanyLogo Logo { get; set; } - - public List Users { get; set; } - } - - public class Customer : Entity - { - private string PrivateProperty { get; set; } - - protected string ProtectedProperty { get; set; } - - public string Name { get; set; } - - public int Age { get; set; } - - public DateTime Birthdate { get; set; } - - public long Id { get; set; } - - public void SetProtected(string value) - { - ProtectedProperty = value; - } - - public Customer() - { - } - - public Customer(string privateProperty) - { - PrivateProperty = privateProperty; - } - } - - public class Entity - { - internal long Version { get; set; } - } - - public class CustomerDto - { - public string Name { get; set; } - - public int Age { get; set; } - - public DateTime Birthdate { get; set; } - } - - public class CustomerType - { - public CustomerType(string code) - { - Code = code; - } - - public string Code { get; private set; } - - public override bool Equals(object obj) - { - return (obj is CustomerType other) && Code == other.Code; - } - - public override int GetHashCode() - { - return Code?.GetHashCode() ?? 0; - } - - public static bool operator ==(CustomerType a, CustomerType b) - { - if (ReferenceEquals(a, b)) - { - return true; - } - - if ((a is null) || (b is null)) - { - return false; - } - - return a.Code == b.Code; - } - - public static bool operator !=(CustomerType a, CustomerType b) - { - return !(a == b); - } - } - - public class DerivedCustomerType : CustomerType - { - public string DerivedInfo { get; set; } - - public DerivedCustomerType(string code) - : base(code) - { - } - } - - public class CustomConvertible : IConvertible - { - private readonly string convertedStringValue; - - public CustomConvertible(string convertedStringValue) - { - this.convertedStringValue = convertedStringValue; - } - - public TypeCode GetTypeCode() - { - throw new InvalidCastException(); - } - - public bool ToBoolean(IFormatProvider provider) - { - throw new InvalidCastException(); - } - - public char ToChar(IFormatProvider provider) - { - throw new InvalidCastException(); - } - - public sbyte ToSByte(IFormatProvider provider) - { - throw new InvalidCastException(); - } - - public byte ToByte(IFormatProvider provider) - { - throw new InvalidCastException(); - } - - public short ToInt16(IFormatProvider provider) - { - throw new InvalidCastException(); - } - - public ushort ToUInt16(IFormatProvider provider) - { - throw new InvalidCastException(); - } - - public int ToInt32(IFormatProvider provider) - { - throw new InvalidCastException(); - } - - public uint ToUInt32(IFormatProvider provider) - { - throw new InvalidCastException(); - } - - public long ToInt64(IFormatProvider provider) - { - throw new InvalidCastException(); - } - - public ulong ToUInt64(IFormatProvider provider) - { - throw new InvalidCastException(); - } - - public float ToSingle(IFormatProvider provider) - { - throw new InvalidCastException(); - } - - public double ToDouble(IFormatProvider provider) - { - throw new InvalidCastException(); - } - - public decimal ToDecimal(IFormatProvider provider) - { - throw new InvalidCastException(); - } - - public DateTime ToDateTime(IFormatProvider provider) - { - throw new InvalidCastException(); - } - - public string ToString(IFormatProvider provider) - { - return convertedStringValue; - } - - public object ToType(Type conversionType, IFormatProvider provider) - { - throw new InvalidCastException(); - } - } - - public abstract class Base - { - public abstract string AbstractProperty { get; } - - public virtual string VirtualProperty => "Foo"; - - public virtual string NonExcludedBaseProperty => "Foo"; - } - - public class Derived : Base - { - public string DerivedProperty1 { get; set; } - - public string DerivedProperty2 { get; set; } - - public override string AbstractProperty => $"{DerivedProperty1} {DerivedProperty2}"; - - public override string VirtualProperty => "Bar"; - - public override string NonExcludedBaseProperty => "Bar"; - - public virtual string NonExcludedDerivedProperty => "Foo"; - } - - #endregion - - #region Nested classes for comparison - - public class ClassWithAllAccessModifiersForMembers - { - public string PublicField; - protected string protectedField; - internal string InternalField; - protected internal string ProtectedInternalField; - private readonly string privateField; - private protected string privateProtectedField; - - public string PublicProperty { get; set; } - - public string ReadOnlyProperty { get; private set; } - - public string WriteOnlyProperty { private get; set; } - - protected string ProtectedProperty { get; set; } - - internal string InternalProperty { get; set; } - - protected internal string ProtectedInternalProperty { get; set; } - - private string PrivateProperty { get; set; } - - private protected string PrivateProtectedProperty { get; set; } - - public ClassWithAllAccessModifiersForMembers(string publicValue, string protectedValue, string internalValue, - string protectedInternalValue, string privateValue, string privateProtectedValue) - { - PublicField = publicValue; - protectedField = protectedValue; - InternalField = internalValue; - ProtectedInternalField = protectedInternalValue; - privateField = privateValue; - privateProtectedField = privateProtectedValue; - - PublicProperty = publicValue; - ReadOnlyProperty = privateValue; - WriteOnlyProperty = privateValue; - ProtectedProperty = protectedValue; - InternalProperty = internalValue; - ProtectedInternalProperty = protectedInternalValue; - PrivateProperty = privateValue; - PrivateProtectedProperty = privateProtectedValue; - } - } - - public class ClassWithValueSemanticsOnSingleProperty - { - public string Key { get; set; } - - public string NestedProperty { get; set; } - - protected bool Equals(ClassWithValueSemanticsOnSingleProperty other) - { - return Key == other.Key; - } - - 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((ClassWithValueSemanticsOnSingleProperty)obj); - } - - public override int GetHashCode() - { - return Key.GetHashCode(); - } - } - - public class Root - { - public string Text { get; set; } - - public Level1 Level { get; set; } - } - - public class Level1 - { - public string Text { get; set; } - - public Level2 Level { get; set; } - } - - public class Level2 - { - public string Text { get; set; } - } - - public class RootDto - { - public string Text { get; set; } - - public Level1Dto Level { get; set; } - } - - public class Level1Dto - { - public string Text { get; set; } - - public Level2Dto Level { get; set; } - } - - public class Level2Dto - { - public string Text { get; set; } - } - - public class CyclicRoot - { - public string Text { get; set; } - - public CyclicLevel1 Level { get; set; } - } - - public class CyclicRootWithValueObject - { - public ValueObject Object { get; set; } - - public CyclicLevelWithValueObject Level { get; set; } - } - - public class ValueObject - { - public ValueObject(string value) - { - Value = value; - } - - public string Value { get; } - - public override bool Equals(object obj) - { - return ((ValueObject)obj).Value == Value; - } - - public override int GetHashCode() - { - return Value.GetHashCode(); - } - } - - public class CyclicLevel1 - { - public string Text { get; set; } - - public CyclicRoot Root { get; set; } - } - - public class CyclicLevelWithValueObject - { - public ValueObject Object { get; set; } - - public CyclicRootWithValueObject Root { get; set; } - } - - public class CyclicRootDto - { - public string Text { get; set; } - - public CyclicLevel1Dto Level { get; set; } - } - - public class CyclicLevel1Dto - { - public string Text { get; set; } - - public CyclicRootDto Root { get; set; } - } - - #endregion - - #region Interfaces for verifying inheritance of properties - - public class Car : Vehicle, ICar - { - public int Wheels { get; set; } - } - - public class ExplicitCar : ExplicitVehicle, ICar - { - public int Wheels { get; set; } - } - - public class Vehicle : IVehicle - { - public int VehicleId { get; set; } - } - - public class ExplicitVehicle : IVehicle - { - int IVehicle.VehicleId { get; set; } - - public int VehicleId { get; set; } - } - - public interface ICar : IVehicle - { - int Wheels { get; set; } - } - - public interface IVehicle - { - int VehicleId { get; set; } - } - - #endregion -} diff --git a/Tests/FluentAssertions.Equivalency.Specs/BasicSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/BasicSpecs.cs new file mode 100644 index 0000000000..470c705f28 --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/BasicSpecs.cs @@ -0,0 +1,619 @@ +using System; +using System.Collections.Generic; +using System.Net; +using FluentAssertions.Extensions; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Equivalency.Specs +{ + public class BasicSpecs + { + [Fact] + public void When_expectation_is_null_it_should_throw() + { + // Arrange + var subject = new { }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(null); + + // Assert + act.Should().Throw().WithMessage( + "Expected subject to be , but found { }*"); + } + + [Fact] + 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" } } }; + + var expectation = new[] { new MyClass() }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation); + + // Assert + act.Should().Throw().WithMessage( + "Expected*subject[0].Items*null*, but found*\"a\"*"); + } + + public class MyClass + { + public IEnumerable Items { get; set; } + } + + [Fact] + public void When_subject_is_null_it_should_throw() + { + // Arrange + SomeDto subject = null; + + // Act + Action act = () => subject.Should().BeEquivalentTo(new { }); + + // Assert + act.Should().Throw().WithMessage( + "Expected subject*to be*, but found *"); + } + + [Fact] + public void When_subject_and_expectation_are_null_it_should_not_throw() + { + // Arrange + SomeDto subject = null; + + // Act + Action act = () => subject.Should().BeEquivalentTo(null); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_subject_and_expectation_are_compared_for_equivalence_it_should_allow_chaining() + { + // Arrange + SomeDto subject = null; + + // Act + Action act = () => subject.Should().BeEquivalentTo(null) + .And.BeNull(); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_subject_and_expectation_are_compared_for_equivalence_with_config_it_should_allow_chaining() + { + // Arrange + SomeDto subject = null; + + // Act + Action act = () => subject.Should().BeEquivalentTo(null, opt => opt) + .And.BeNull(); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_subject_and_expectation_are_compared_for_non_equivalence_it_should_allow_chaining() + { + // Arrange + SomeDto subject = null; + + // Act + Action act = () => subject.Should().NotBeEquivalentTo(new { }) + .And.BeNull(); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_subject_and_expectation_are_compared_for_non_equivalence_with_config_it_should_allow_chaining() + { + // Arrange + SomeDto subject = null; + + // Act + Action act = () => subject.Should().NotBeEquivalentTo(new { }, opt => opt) + .And.BeNull(); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_equivalence_on_a_value_type_from_system_it_should_not_do_a_structural_comparision() + { + // Arrange + + // DateTime is used as an example because the current implementation + // would hit the recursion-depth limit if structural equivalence were attempted. + var date1 = new { Property = 1.January(2011) }; + + var date2 = new { Property = 1.January(2011) }; + + // Act + Action act = () => date1.Should().BeEquivalentTo(date2); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_an_object_hides_object_equals_it_should_be_compared_using_its_members() + { + // Arrange + var actual = new VirtualClassOverride { Property = "Value", OtherProperty = "Actual" }; + + var expected = new VirtualClassOverride { Property = "Value", OtherProperty = "Expected" }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected); + + // Assert + act.Should().Throw("*OtherProperty*Expected*Actual*"); + } + + public class VirtualClass + { + public string Property { get; set; } + + public new virtual bool Equals(object obj) + { + return (obj is VirtualClass other) && other.Property == Property; + } + } + + public class VirtualClassOverride : VirtualClass + { + public string OtherProperty { get; set; } + } + + [Fact] + 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" } + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected, + options => options.ComparingByMembers()); + + // Assert + act.Should().Throw().WithMessage("*NestedProperty*OtherValue*SomeValue*"); + } + + [Fact] + public void When_treating_a_value_type_as_a_complex_type_it_should_compare_them_by_members() + { + // Arrange + var subject = new ClassWithValueSemanticsOnSingleProperty { Key = "SameKey", NestedProperty = "SomeValue" }; + + var expected = new ClassWithValueSemanticsOnSingleProperty { Key = "SameKey", NestedProperty = "OtherValue" }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected, + options => options.ComparingByMembers()); + + // Assert + act.Should().Throw().WithMessage("*NestedProperty*OtherValue*SomeValue*"); + } + + [Fact] + public void When_treating_a_type_as_value_type_but_it_was_already_marked_as_reference_type_it_should_throw() + { + // Arrange + var subject = new ClassWithValueSemanticsOnSingleProperty { Key = "Don't care" }; + + var expected = new ClassWithValueSemanticsOnSingleProperty { Key = "Don't care" }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected, options => options + .ComparingByMembers() + .ComparingByValue()); + + // Assert + act.Should().Throw().WithMessage( + $"*compare {nameof(ClassWithValueSemanticsOnSingleProperty)}*value*already*members*"); + } + + [Fact] + public void When_treating_a_type_as_reference_type_but_it_was_already_marked_as_value_type_it_should_throw() + { + // Arrange + var subject = new ClassWithValueSemanticsOnSingleProperty { Key = "Don't care" }; + + var expected = new ClassWithValueSemanticsOnSingleProperty { Key = "Don't care" }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected, options => options + .ComparingByValue() + .ComparingByMembers()); + + // Assert + act.Should().Throw().WithMessage( + $"*compare {nameof(ClassWithValueSemanticsOnSingleProperty)}*members*already*value*"); + } + + [Fact] + public void When_treating_a_complex_type_in_a_collection_as_a_value_type_it_should_compare_them_by_value() + { + // Arrange + var subject = new[] { new { Address = IPAddress.Parse("1.2.3.4"), Word = "a" } }; + + var expected = new[] { new { Address = IPAddress.Parse("1.2.3.4"), Word = "a" } }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected, + options => options.ComparingByValue()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_treating_a_complex_type_as_a_value_type_it_should_compare_them_by_value() + { + // Arrange + var subject = new { Address = IPAddress.Parse("1.2.3.4"), Word = "a" }; + + var expected = new { Address = IPAddress.Parse("1.2.3.4"), Word = "a" }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected, + options => options.ComparingByValue()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_treating_a_null_type_as_value_type_it_should_throw() + { + // Arrange + var subject = new object(); + var expected = new object(); + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected, opt => opt + .ComparingByValue(null)); + + // Assert + act.Should().Throw() + .WithParameterName("type"); + } + + [Fact] + public void When_treating_a_null_type_as_reference_type_it_should_throw() + { + // Arrange + var subject = new object(); + var expected = new object(); + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected, opt => opt + .ComparingByMembers(null)); + + // Assert + act.Should().Throw() + .WithParameterName("type"); + } + + [Fact] + 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 }); + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected, opt => opt + .ComparingByMembers(typeof(Option<>))); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + 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 }); + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected, opt => opt + .ComparingByMembers(typeof(Option<>)) + .ComparingByValue>()); + + // Assert + act.Should().Throw(); + } + + [Fact] + 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 }); + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected, opt => opt + .ComparingByValue(typeof(Option<>)) + .ComparingByMembers>()); + + // Assert + act.Should().NotThrow(); + } + + private struct Option : IEquatable> + where T : class + { + public T Value { get; } + + public Option(T value) + { + Value = value; + } + + public bool Equals(Option other) => + EqualityComparer.Default.Equals(Value, other.Value); + + public override bool Equals(object obj) => + obj is Option other && Equals(other); + + public override int GetHashCode() => Value?.GetHashCode() ?? 0; + } + + [Fact] + public void When_threating_any_type_as_reference_type_it_should_exclude_primitive_types() + { + // Arrange + var subject = new { Value = 1 }; + var expected = new { Value = 2 }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected, opt => opt + .ComparingByMembers()); + + // Assert + act.Should().Throw() + .WithMessage("*be 2*found 1*"); + } + + [Fact] + public void When_threating_an_open_type_as_reference_type_it_should_exclude_primitive_types() + { + // Arrange + var subject = new { Value = 1 }; + var expected = new { Value = 2 }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected, opt => opt + .ComparingByMembers(typeof(IEquatable<>))); + + // Assert + act.Should().Throw() + .WithMessage("*be 2*found 1*"); + } + + [Fact] + public void When_threating_a_primitive_type_as_a_reference_type_it_should_throw() + { + // Arrange + var subject = new { Value = 1 }; + var expected = new { Value = 2 }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected, opt => opt + .ComparingByMembers()); + + // Assert + act.Should().Throw() + .WithMessage("*Cannot compare a primitive type*Int32*"); + } + + [Fact] + public void When_a_type_originates_from_the_System_namespace_it_should_be_treated_as_a_value_type() + { + // Arrange + var subject = new { UriBuilder = new UriBuilder("http://localhost:9001/api"), }; + + var expected = new { UriBuilder = new UriBuilder("https://localhost:9002/bapi"), }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected); + + // Assert + act.Should().Throw() + .WithMessage("Expected*UriBuilder* to be https://localhost:9002/bapi, but found http://localhost:9001/api*"); + } + + [Fact] + public void When_asserting_equivalence_on_a_string_it_should_use_string_specific_failure_messages() + { + // Arrange + string s1 = "hello"; + string s2 = "good-bye"; + + // Act + Action act = () => s1.Should().BeEquivalentTo(s2); + + // Assert + act.Should().Throw() + .WithMessage("*to be*\"good-bye\" with a length of 8, but \"hello\" has a length of 5*"); + } + + [Fact] + public void When_asserting_equivalence_of_strings_typed_as_objects_it_should_compare_them_as_strings() + { + // Arrange + + // The convoluted construction is so the compiler does not optimize the two objects to be the same. + object s1 = new string('h', 2); + object s2 = "hh"; + + // Act + Action act = () => s1.Should().BeEquivalentTo(s2); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_equivalence_of_ints_typed_as_objects_it_should_use_the_runtime_type() + { + // Arrange + object s1 = 1; + object s2 = 1; + + // Act + Action act = () => s1.Should().BeEquivalentTo(s2); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_all_field_of_the_object_are_equal_equivalency_should_pass() + { + // Arrange + var object1 = new ClassWithOnlyAField { Value = 1 }; + var object2 = new ClassWithOnlyAField { Value = 1 }; + + // Act + Action act = () => object1.Should().BeEquivalentTo(object2); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_number_values_are_convertible_it_should_treat_them_as_equivalent() + { + // Arrange + var actual = new Dictionary { ["001"] = 1L, ["002"] = 2L }; + + var expected = new Dictionary { ["001"] = 1, ["002"] = 2 }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_all_field_of_the_object_are_not_equal_equivalency_should_fail() + { + // Arrange + var object1 = new ClassWithOnlyAField { Value = 1 }; + var object2 = new ClassWithOnlyAField { Value = 101 }; + + // Act + Action act = () => object1.Should().BeEquivalentTo(object2); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_field_on_the_subject_matches_a_property_the_members_should_match_for_equivalence() + { + // Arrange + var onlyAField = new ClassWithOnlyAField { Value = 1 }; + var onlyAProperty = new ClassWithOnlyAProperty { Value = 101 }; + + // Act + Action act = () => onlyAField.Should().BeEquivalentTo(onlyAProperty); + + // Assert + act.Should().Throw().WithMessage("Expected property onlyAField.Value*to be 101, but found 1.*"); + } + + [Fact] + public void When_asserting_equivalence_including_only_fields_it_should_not_match_properties() + { + // Arrange + var onlyAField = new ClassWithOnlyAField { Value = 1 }; + object onlyAProperty = new ClassWithOnlyAProperty { Value = 101 }; + + // Act + Action act = () => onlyAProperty.Should().BeEquivalentTo(onlyAField, opts => opts.ExcludingProperties()); + + // Assert + act.Should().Throw() + .WithMessage("Expectation has field onlyAProperty.Value that the other object does not have.*"); + } + + [Fact] + public void When_asserting_equivalence_including_only_properties_it_should_not_match_fields() + { + // Arrange + var onlyAField = new ClassWithOnlyAField { Value = 1 }; + var onlyAProperty = new ClassWithOnlyAProperty { Value = 101 }; + + // Act + Action act = () => onlyAField.Should().BeEquivalentTo(onlyAProperty, opts => opts.IncludingAllDeclaredProperties()); + + // Assert + act.Should().Throw() + .WithMessage("Expectation has property onlyAField.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() + { + // Arrange + var record = new { Member1 = "", Member2 = new[] { "", "" } }; + + var record2 = new { Member1 = "different", Member2 = new[] { "", "" } }; + + // Act + Action act = () => record.Should().BeEquivalentTo(record2); + + // Assert + act.Should().Throw().WithMessage( + @"Expected property record.Member1* to be*""different"" with a length of 9, but*"""" has a length of 0*"); + } + + [Fact] + public void When_asserting_object_equivalence_against_a_null_value_it_should_properly_throw() + { + // Act + Action act = () => ((object)null).Should().BeEquivalentTo("foo"); + + // Assert + act.Should().Throw().WithMessage("*foo*null*"); + } + + [Fact] + public void When_the_graph_contains_guids_it_should_properly_format_them() + { + // Arrange + var actual = + new[] { new { Id = Guid.NewGuid(), Name = "Name" } }; + + var expected = + new[] { new { Id = Guid.NewGuid(), Name = "Name" } }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected); + + // Assert + act.Should().Throw().WithMessage("Expected property actual[0].Id*to be *-*, but found *-*"); + } + } +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/CollectionEquivalencySpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/CollectionSpecs.cs similarity index 69% rename from Tests/FluentAssertions.Equivalency.Specs/CollectionEquivalencySpecs.cs rename to Tests/FluentAssertions.Equivalency.Specs/CollectionSpecs.cs index bcfb8d9b9c..d9346d4fa4 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/CollectionEquivalencySpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/CollectionSpecs.cs @@ -4,15 +4,13 @@ using System.Collections.Immutable; using System.Collections.ObjectModel; using System.Linq; -using FluentAssertions.Equivalency; using FluentAssertions.Extensions; -using FluentAssertions.Specs.Primitives; using Xunit; using Xunit.Sdk; -namespace FluentAssertions.Specs.Equivalency +namespace FluentAssertions.Equivalency.Specs { - public class CollectionEquivalencySpecs + public class CollectionSpecs { public interface IInterface { @@ -38,7 +36,7 @@ public SubDummy(int id) public override bool Equals(object obj) { return obj is SubDummy subDummy - && Id == subDummy.Id; + && Id == subDummy.Id; } public override int GetHashCode() @@ -277,18 +275,13 @@ public void When_a_byte_array_does_not_match_strictly_and_order_is_not_strict_it } [Fact] - public void When_a_collection_property_is_a_byte_array_which_does_not_match_strictly_and_order_is_not_strict_it_should_throw() + public void + When_a_collection_property_is_a_byte_array_which_does_not_match_strictly_and_order_is_not_strict_it_should_throw() { // Arrange - var subject = new - { - bytes = new byte[] { 1, 2, 3, 4, 5, 6 } - }; + var subject = new { bytes = new byte[] { 1, 2, 3, 4, 5, 6 } }; - var expectation = new - { - bytes = new byte[] { 6, 5, 4, 3, 2, 1 } - }; + var expectation = new { bytes = new byte[] { 6, 5, 4, 3, 2, 1 } }; // Act Action action = () => subject.Should().BeEquivalentTo(expectation, options => options.WithoutStrictOrdering()); @@ -336,10 +329,7 @@ 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 @@ -352,33 +342,20 @@ public void When_a_nullable_collection_does_not_match_it_should_throw() } [Fact] - public void When_a_collection_contains_a_reference_to_an_object_that_is_also_in_its_parent_it_should_not_be_treated_as_a_cyclic_reference() + public void + When_a_collection_contains_a_reference_to_an_object_that_is_also_in_its_parent_it_should_not_be_treated_as_a_cyclic_reference() { // Arrange - var logbook = new BasicEquivalencySpecs.LogbookCode("SomeKey"); + var logbook = new LogbookCode("SomeKey"); - var logbookEntry = new BasicEquivalencySpecs.LogbookEntryProjection + var logbookEntry = new LogbookEntryProjection { - Logbook = logbook, - LogbookRelations = new[] - { - new BasicEquivalencySpecs.LogbookRelation - { - Logbook = logbook - } - } + Logbook = logbook, LogbookRelations = new[] { new LogbookRelation { Logbook = logbook } } }; - var equivalentLogbookEntry = new BasicEquivalencySpecs.LogbookEntryProjection + var equivalentLogbookEntry = new LogbookEntryProjection { - Logbook = logbook, - LogbookRelations = new[] - { - new BasicEquivalencySpecs.LogbookRelation - { - Logbook = logbook - } - } + Logbook = logbook, LogbookRelations = new[] { new LogbookRelation { Logbook = logbook } } }; // Act @@ -398,32 +375,14 @@ public void When_a_collection_contains_less_items_than_expected_it_should_throw( { Customers = new[] { - new Customer - { - Age = 38, - Birthdate = 20.September(1973), - Name = "John" - }, - new Customer - { - Age = 38, - Birthdate = 20.September(1973), - Name = "Jane" - } + new Customer { Age = 38, Birthdate = 20.September(1973), Name = "John" }, + new Customer { Age = 38, Birthdate = 20.September(1973), Name = "Jane" } } }; var subject = new { - Customers = new[] - { - new CustomerDto - { - Age = 24, - Birthdate = 21.September(1973), - Name = "John" - } - } + Customers = new[] { new CustomerDto { Age = 24, Birthdate = 21.September(1973), Name = "John" } } }; // Act @@ -439,35 +398,14 @@ public void When_a_collection_contains_less_items_than_expected_it_should_throw( public void When_a_collection_contains_more_items_than_expected_it_should_throw() { // Arrange - var expected = new - { - Customers = new[] - { - new Customer - { - Age = 38, - Birthdate = 20.September(1973), - Name = "John" - } - } - }; + var expected = new { Customers = new[] { new Customer { Age = 38, Birthdate = 20.September(1973), Name = "John" } } }; var subject = new { Customers = new[] { - new CustomerDto - { - Age = 38, - Birthdate = 20.September(1973), - Name = "Jane" - }, - new CustomerDto - { - Age = 24, - Birthdate = 21.September(1973), - Name = "John" - } + new CustomerDto { Age = 38, Birthdate = 20.September(1973), Name = "Jane" }, + new CustomerDto { Age = 24, Birthdate = 21.September(1973), Name = "John" } } }; @@ -488,18 +426,8 @@ public void When_a_collection_property_contains_objects_with_matching_properties { Customers = new[] { - new Customer - { - Age = 32, - Birthdate = 31.July(1978), - Name = "Jane" - }, - new Customer - { - Age = 38, - Birthdate = 20.September(1973), - Name = "John" - } + new Customer { Age = 32, Birthdate = 31.July(1978), Name = "Jane" }, + new Customer { Age = 38, Birthdate = 20.September(1973), Name = "John" } } }; @@ -507,18 +435,8 @@ public void When_a_collection_property_contains_objects_with_matching_properties { Customers = new[] { - new CustomerDto - { - Age = 38, - Birthdate = 20.September(1973), - Name = "John" - }, - new CustomerDto - { - Age = 32, - Birthdate = 31.July(1978), - Name = "Jane" - } + new CustomerDto { Age = 38, Birthdate = 20.September(1973), Name = "John" }, + new CustomerDto { Age = 32, Birthdate = 31.July(1978), Name = "Jane" } } }; @@ -533,19 +451,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 int[] { 42 } - }; + var items = new[] { new int[0], new int[] { 42 } }; // Act / Assert items.Should().BeEquivalentTo( - new[] - { - new int[] { 42 }, - new int[0] - } + new[] { new int[] { 42 }, new int[0] } ); } @@ -553,30 +463,11 @@ public void When_two_deeply_nested_collections_are_equivalent_while_ignoring_the public void When_a_collection_property_contains_objects_with_mismatching_properties_it_should_throw() { // Arrange - var expected = new - { - Customers = new[] - { - new Customer - { - Age = 38, - Birthdate = 20.September(1973), - Name = "John" - } - } - }; + var expected = new { Customers = new[] { new Customer { Age = 38, Birthdate = 20.September(1973), Name = "John" } } }; var subject = new { - Customers = new[] - { - new CustomerDto - { - Age = 38, - Birthdate = 20.September(1973), - Name = "Jane" - } - } + Customers = new[] { new CustomerDto { Age = 38, Birthdate = 20.September(1973), Name = "Jane" } } }; // Act @@ -603,23 +494,9 @@ public void When_the_subject_is_a_non_generic_collection_it_should_still_work() public void When_a_collection_property_was_expected_but_the_property_is_not_a_collection_it_should_throw() { // Arrange - var subject = new - { - Customers = "Jane, John" - }; + var subject = new { Customers = "Jane, John" }; - var expected = new - { - Customers = new[] - { - new Customer - { - Age = 38, - Birthdate = 20.September(1973), - Name = "John" - } - } - }; + var expected = new { Customers = new[] { new Customer { Age = 38, Birthdate = 20.September(1973), Name = "John" } } }; // Act Action act = () => subject.Should().BeEquivalentTo(expected); @@ -634,31 +511,9 @@ public void When_a_collection_property_was_expected_but_the_property_is_not_a_co public void When_a_complex_object_graph_with_collections_matches_expectations_it_should_not_throw() { // Arrange - var subject = new - { - Bytes = new byte[] - { - 1, 2, 3, 4 - }, - Object = new - { - A = 1, - B = 2 - } - }; + var subject = new { Bytes = new byte[] { 1, 2, 3, 4 }, Object = new { A = 1, B = 2 } }; - var expected = new - { - Bytes = new byte[] - { - 1, 2, 3, 4 - }, - Object = new - { - A = 1, - B = 2 - } - }; + var expected = new { Bytes = new byte[] { 1, 2, 3, 4 }, Object = new { A = 1, B = 2 } }; // Act Action act = () => subject.Should().BeEquivalentTo(expected); @@ -677,15 +532,8 @@ public void When_a_deeply_nested_property_of_a_collection_with_an_invalid_value_ Level = new { Text = "Level1", - Level = new - { - Text = "Level2" - }, - Collection = new[] - { - new { Number = 1, Text = "Text" }, - new { Number = 2, Text = "Actual" } - } + Level = new { Text = "Level2" }, + Collection = new[] { new { Number = 1, Text = "Text" }, new { Number = 2, Text = "Actual" } } } }; @@ -695,15 +543,8 @@ public void When_a_deeply_nested_property_of_a_collection_with_an_invalid_value_ Level = new { Text = "Level1", - Level = new - { - Text = "Level2" - }, - Collection = new[] - { - new { Number = 1, Text = "Text" }, - new { Number = 2, Text = "Expected" } - } + Level = new { Text = "Level2" }, + Collection = new[] { new { Number = 1, Text = "Text" }, new { Number = 2, Text = "Expected" } } } }; @@ -719,23 +560,9 @@ public void When_a_deeply_nested_property_of_a_collection_with_an_invalid_value_ public void When_a_dictionary_property_is_detected_it_should_ignore_the_order_of_the_pairs() { // Arrange - var expected = new - { - Customers = new Dictionary - { - ["Key2"] = "Value2", - ["Key1"] = "Value1" - } - }; + var expected = new { Customers = new Dictionary { ["Key2"] = "Value2", ["Key1"] = "Value1" } }; - var subject = new - { - Customers = new Dictionary - { - ["Key1"] = "Value1", - ["Key2"] = "Value2" - } - }; + var subject = new { Customers = new Dictionary { ["Key1"] = "Value1", ["Key2"] = "Value2" } }; // Act Action act = () => subject.Should().BeEquivalentTo(expected); @@ -760,7 +587,8 @@ public void When_injecting_a_null_config_it_should_throw() } [Fact] - public void When_a_object_implements_multiple_IEnumerable_interfaces_but_the_declared_type_is_assignable_to_only_one_and_runtime_checking_is_configured_it_should_fail() + public void + When_a_object_implements_multiple_IEnumerable_interfaces_but_the_declared_type_is_assignable_to_only_one_and_runtime_checking_is_configured_it_should_fail() { // Arrange IEnumerable collection1 = new EnumerableOfStringAndObject(); @@ -779,20 +607,9 @@ public void When_a_object_implements_multiple_IEnumerable_interfaces_but_the_dec public void When_a_specific_property_is_included_it_should_ignore_the_rest_of_the_properties() { // Arrange - var result = new[] - { - new - { - A = "aaa", - B = "bbb" - } - }; + var result = new[] { new { A = "aaa", B = "bbb" } }; - var expected = new - { - A = "aaa", - B = "ccc" - }; + var expected = new { A = "aaa", B = "ccc" }; // Act Action act = () => result.Should().BeEquivalentTo(new[] { expected }, options => options.Including(x => x.A)); @@ -802,7 +619,8 @@ public void When_a_specific_property_is_included_it_should_ignore_the_rest_of_th } [Fact] - public void When_a_strongly_typed_collection_is_declared_as_an_untyped_collection_and_runtime_checking_is_configured_is_should_use_the_runtime_type() + public void + When_a_strongly_typed_collection_is_declared_as_an_untyped_collection_and_runtime_checking_is_configured_is_should_use_the_runtime_type() { // Arrange ICollection collection1 = new List { new Car() }; @@ -888,7 +706,8 @@ public void When_more_than_10_strings_in_the_collection_are_not_equal_to_expecte } [Fact] - public void When_some_strings_in_the_collection_are_not_equal_to_expected_string_for_huge_table_execution_time_should_still_be_short() + public void + When_some_strings_in_the_collection_are_not_equal_to_expected_string_for_huge_table_execution_time_should_still_be_short() { // Arrange const int N = 100000; @@ -929,9 +748,7 @@ public void When_all_subject_items_are_equivalent_to_expectation_object_it_shoul // Act Action action = () => subject.Should().AllBeEquivalentTo(new { - Name = "someDto", - Age = 1, - Birthdate = default(DateTime) + Name = "someDto", Age = 1, Birthdate = default(DateTime) }); // Assert @@ -951,12 +768,10 @@ public void When_all_subject_items_are_equivalent_to_expectation_object_it_shoul // Act Action action = () => subject.Should().AllBeEquivalentTo(new - { - Name = "someDto", - Age = 1, - Birthdate = default(DateTime) - }) - .And.HaveCount(3); + { + Name = "someDto", Age = 1, Birthdate = default(DateTime) + }) + .And.HaveCount(3); // Assert action.Should().NotThrow(); @@ -992,7 +807,8 @@ public void When_more_than_10_subjects_items_are_not_equivalent_to_expectation_o } [Fact] - public void When_some_subject_items_are_not_equivalent_to_expectation_for_huge_table_execution_time_should_still_be_short() + public void + When_some_subject_items_are_not_equivalent_to_expectation_for_huge_table_execution_time_should_still_be_short() { // Arrange const int N = 100000; @@ -1020,7 +836,8 @@ public void When_some_subject_items_are_not_equivalent_to_expectation_for_huge_t } [Fact] - public void When_an_object_implements_multiple_IEnumerable_interfaces_but_the_declared_type_is_assignable_to_only_one_it_should_respect_the_declared_type() + public void + When_an_object_implements_multiple_IEnumerable_interfaces_but_the_declared_type_is_assignable_to_only_one_it_should_respect_the_declared_type() { // Arrange IEnumerable collection1 = new EnumerableOfStringAndObject(); @@ -1039,30 +856,14 @@ public void When_an_unordered_collection_must_be_strict_using_a_predicate_it_sho // Arrange var subject = new[] { - new - { - Name = "John", - UnorderedCollection = new[] { 1, 2, 3, 4, 5 } - }, - new - { - Name = "Jane", - UnorderedCollection = new int[0] - } + new { Name = "John", UnorderedCollection = new[] { 1, 2, 3, 4, 5 } }, + new { Name = "Jane", UnorderedCollection = new int[0] } }; var expectation = new[] { - new - { - Name = "John", - UnorderedCollection = new[] { 5, 4, 3, 2, 1 } - }, - new - { - Name = "Jane", - UnorderedCollection = new int[0] - } + new { Name = "John", UnorderedCollection = new[] { 5, 4, 3, 2, 1 } }, + new { Name = "Jane", UnorderedCollection = new int[0] } }; // Act @@ -1075,35 +876,20 @@ public void When_an_unordered_collection_must_be_strict_using_a_predicate_it_sho } [Fact] - public void When_an_unordered_collection_must_be_strict_using_a_predicate_and_order_was_reset_to_not_strict_it_should_not_throw() + public void + When_an_unordered_collection_must_be_strict_using_a_predicate_and_order_was_reset_to_not_strict_it_should_not_throw() { // Arrange var subject = new[] { - new - { - Name = "John", - UnorderedCollection = new[] { 1, 2, 3, 4, 5 } - }, - new - { - Name = "Jane", - UnorderedCollection = new int[0] - } + new { Name = "John", UnorderedCollection = new[] { 1, 2, 3, 4, 5 } }, + new { Name = "Jane", UnorderedCollection = new int[0] } }; var expectation = new[] { - new - { - Name = "John", - UnorderedCollection = new[] { 5, 4, 3, 2, 1 } - }, - new - { - Name = "Jane", - UnorderedCollection = new int[0] - } + new { Name = "John", UnorderedCollection = new[] { 5, 4, 3, 2, 1 } }, + new { Name = "Jane", UnorderedCollection = new int[0] } }; // Act @@ -1122,30 +908,14 @@ public void When_an_unordered_collection_must_be_strict_using_an_expression_it_s // Arrange var subject = new[] { - new - { - Name = "John", - UnorderedCollection = new[] { 1, 2, 3, 4, 5 } - }, - new - { - Name = "Jane", - UnorderedCollection = new int[0] - } + new { Name = "John", UnorderedCollection = new[] { 1, 2, 3, 4, 5 } }, + new { Name = "Jane", UnorderedCollection = new int[0] } }; var expectation = new[] { - new - { - Name = "John", - UnorderedCollection = new[] { 5, 4, 3, 2, 1 } - }, - new - { - Name = "Jane", - UnorderedCollection = new int[0] - } + new { Name = "John", UnorderedCollection = new[] { 5, 4, 3, 2, 1 } }, + new { Name = "Jane", UnorderedCollection = new int[0] } }; // Act @@ -1163,35 +933,20 @@ public void When_an_unordered_collection_must_be_strict_using_an_expression_it_s } [Fact] - public void When_an_unordered_collection_must_be_strict_using_an_expression_and_order_is_reset_to_not_strict_it_should_not_throw() + public void + When_an_unordered_collection_must_be_strict_using_an_expression_and_order_is_reset_to_not_strict_it_should_not_throw() { // Arrange var subject = new[] { - new - { - Name = "John", - UnorderedCollection = new[] { 1, 2, 3, 4, 5 } - }, - new - { - Name = "Jane", - UnorderedCollection = new int[0] - } + new { Name = "John", UnorderedCollection = new[] { 1, 2, 3, 4, 5 } }, + new { Name = "Jane", UnorderedCollection = new int[0] } }; var expectation = new[] { - new - { - Name = "John", - UnorderedCollection = new[] { 5, 4, 3, 2, 1 } - }, - new - { - Name = "Jane", - UnorderedCollection = new int[0] - } + new { Name = "John", UnorderedCollection = new[] { 5, 4, 3, 2, 1 } }, + new { Name = "Jane", UnorderedCollection = new int[0] } }; // Act @@ -1212,30 +967,14 @@ public void When_an_unordered_collection_must_not_be_strict_using_a_predicate_it // Arrange var subject = new[] { - new - { - Name = "John", - UnorderedCollection = new[] { 1, 2, 3, 4, 5 } - }, - new - { - Name = "Jane", - UnorderedCollection = new int[0] - } + new { Name = "John", UnorderedCollection = new[] { 1, 2, 3, 4, 5 } }, + new { Name = "Jane", UnorderedCollection = new int[0] } }; var expectation = new[] { - new - { - Name = "John", - UnorderedCollection = new[] { 5, 4, 3, 2, 1 } - }, - new - { - Name = "Jane", - UnorderedCollection = new int[0] - } + new { Name = "John", UnorderedCollection = new[] { 5, 4, 3, 2, 1 } }, + new { Name = "Jane", UnorderedCollection = new int[0] } }; // Act @@ -1248,35 +987,20 @@ public void When_an_unordered_collection_must_not_be_strict_using_a_predicate_it } [Fact] - public void When_an_unordered_collection_must_not_be_strict_using_a_predicate_and_order_was_reset_to_strict_it_should_throw() + public void + When_an_unordered_collection_must_not_be_strict_using_a_predicate_and_order_was_reset_to_strict_it_should_throw() { // Arrange var subject = new[] { - new - { - Name = "John", - UnorderedCollection = new[] { 1, 2 } - }, - new - { - Name = "Jane", - UnorderedCollection = new int[0] - } + new { Name = "John", UnorderedCollection = new[] { 1, 2 } }, + new { Name = "Jane", UnorderedCollection = new int[0] } }; var expectation = new[] { - new - { - Name = "John", - UnorderedCollection = new[] { 2, 1 } - }, - new - { - Name = "Jane", - UnorderedCollection = new int[0] - } + new { Name = "John", UnorderedCollection = new[] { 2, 1 } }, + new { Name = "Jane", UnorderedCollection = new int[0] } }; // Act @@ -1292,7 +1016,8 @@ public void When_an_unordered_collection_must_not_be_strict_using_a_predicate_an } [Fact] - public void When_asserting_equivalence_of_collections_and_configured_to_use_runtime_properties_it_should_respect_the_runtime_type() + 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() }); @@ -1343,44 +1068,14 @@ public void When_custom_assertion_rules_are_utilized_the_rules_should_be_respect // Arrange var subject = new[] { - new - { - Value = new Customer - { - Name = "John", - Age = 31, - Id = 1 - } - }, - new - { - Value =new Customer - { - Name = "Jane", - Age = 24, - Id = 2 - } - } + new { Value = new Customer { Name = "John", Age = 31, Id = 1 } }, + new { Value = new Customer { Name = "Jane", Age = 24, Id = 2 } } }; var expectation = new[] { - new - { - Value = new CustomerDto - { - Name = "John", - Age = 30 - } - }, - new - { - Value = new CustomerDto - { - Name = "Jane", - Age = 24 - } - } + new { Value = new CustomerDto { Name = "John", Age = 30 } }, + new { Value = new CustomerDto { Name = "Jane", Age = 24 } } }; // Act @@ -1413,22 +1108,12 @@ public void When_nested_objects_are_excluded_from_collections_it_should_use_simp // Arrange var actual = new MyObject { - MyString = "identical string", - Child = new ClassIdentifiedById - { - Id = 1, - MyChildString = "identical string" - } + MyString = "identical string", Child = new ClassIdentifiedById { Id = 1, MyChildString = "identical string" } }; var expectation = new MyObject { - MyString = "identical string", - Child = new ClassIdentifiedById - { - Id = 1, - MyChildString = "DIFFERENT STRING" - } + MyString = "identical string", Child = new ClassIdentifiedById { Id = 1, MyChildString = "DIFFERENT STRING" } }; IList actualList = new List { actual }; @@ -1447,34 +1132,12 @@ public void When_no_collection_item_matches_it_should_report_the_closest_match() // Arrange var subject = new List { - new Customer - { - Name = "John", - Age = 27, - Id = 1 - }, - new Customer - { - Name = "Jane", - Age = 30, - Id = 2 - } + new Customer { Name = "John", Age = 27, Id = 1 }, new Customer { Name = "Jane", Age = 30, Id = 2 } }; var expectation = new List { - new Customer - { - Name = "Jane", - Age = 30, - Id = 2 - }, - new Customer - { - Name = "John", - Age = 28, - Id = 1 - } + new Customer { Name = "Jane", Age = 30, Id = 2 }, new Customer { Name = "John", Age = 28, Id = 1 } }; // Act @@ -1492,42 +1155,14 @@ public void When_only_a_deeply_nested_property_is_included_it_should_exclude_the // Arrange var actualObjects = new[] { - new - { - SubObject = new - { - Property1 = "John", - Property2 = "John" - } - }, - new - { - SubObject = new - { - Property1 = "John", - Property2 = "John" - } - } + new { SubObject = new { Property1 = "John", Property2 = "John" } }, + new { SubObject = new { Property1 = "John", Property2 = "John" } } }; var expectedObjects = new[] { - new - { - SubObject = new - { - Property1 = "John", - Property2 = "John" - } - }, - new - { - SubObject = new - { - Property1 = "John", - Property2 = "Jane" - } - } + new { SubObject = new { Property1 = "John", Property2 = "John" } }, + new { SubObject = new { Property1 = "John", Property2 = "Jane" } } }; // Act @@ -1701,7 +1336,8 @@ public void When_not_all_subject_items_are_equivalent_to_expectation_object_with var subject = new List { 'g', 'a' }; // Act - Action action = () => subject.Should().AllBeEquivalentTo('g', opt => opt, "we want to test the failure {0}", "message"); + Action action = () => + subject.Should().AllBeEquivalentTo('g', opt => opt, "we want to test the failure {0}", "message"); // Assert action.Should().Throw() @@ -1742,8 +1378,8 @@ public void When_the_expectation_is_null_it_should_throw() // Arrange var actual = new[,] { - { 1, 2, 3 }, - { 4, 5, 6 } + { 1, 2, 3 }, + { 4, 5, 6 } }; // Act @@ -1804,10 +1440,7 @@ public void When_the_length_of_the_2nd_dimension_differs_between_the_arrays_it_s { 4, 5, 6 } }; - var expectation = new[,] - { - { 1, 2, 3 } - }; + var expectation = new[,] { { 1, 2, 3 } }; // Act Action act = () => actual.Should().BeEquivalentTo(expectation); @@ -1845,7 +1478,7 @@ public void When_the_length_of_the_first_dimension_differs_between_the_arrays_it public void When_the_number_of_dimensions_of_the_arrays_are_not_the_same_it_should_throw() { // Arrange - var actual = new[, ,] + var actual = new[,,] { { { 1 }, @@ -1877,22 +1510,9 @@ public void When_the_number_of_dimensions_of_the_arrays_are_not_the_same_it_shou public void When_the_other_dictionary_does_not_contain_enough_items_it_should_throw() { // Arrange - var expected = new - { - Customers = new Dictionary - { - ["Key1"] = "Value1", - ["Key2"] = "Value2" - } - }; + var expected = new { Customers = new Dictionary { ["Key1"] = "Value1", ["Key2"] = "Value2" } }; - var subject = new - { - Customers = new Dictionary - { - ["Key1"] = "Value1" - } - }; + var subject = new { Customers = new Dictionary { ["Key1"] = "Value1" } }; // Act Action act = () => subject.Should().BeEquivalentTo(expected); @@ -1906,19 +1526,9 @@ public void When_the_other_dictionary_does_not_contain_enough_items_it_should_th public void When_the_other_property_is_not_a_dictionary_it_should_throw() { // Arrange - var expected = new - { - Customers = "I am a string" - }; + var expected = new { Customers = "I am a string" }; - var subject = new - { - Customers = new Dictionary - { - ["Key2"] = "Value2", - ["Key1"] = "Value1" - } - }; + var subject = new { Customers = new Dictionary { ["Key2"] = "Value2", ["Key1"] = "Value1" } }; // Act Action act = () => subject.Should().BeEquivalentTo(expected); @@ -1929,7 +1539,8 @@ public void When_the_other_property_is_not_a_dictionary_it_should_throw() } [Fact] - public void When_the_root_object_is_referenced_from_an_object_in_a_nested_collection_it_should_treat_it_as_a_cyclic_reference() + public void + When_the_root_object_is_referenced_from_an_object_in_a_nested_collection_it_should_treat_it_as_a_cyclic_reference() { // Arrange var company1 = new MyCompany { Name = "Company" }; @@ -1955,30 +1566,11 @@ public void When_the_root_object_is_referenced_from_an_object_in_a_nested_collec public void When_the_subject_contains_less_items_than_expected_it_should_throw() { // Arrange - var subject = new List - { - new Customer - { - Name = "John", - Age = 27, - Id = 1 - } - }; + var subject = new List { new Customer { Name = "John", Age = 27, Id = 1 } }; var expectation = new List { - new Customer - { - Name = "John", - Age = 27, - Id = 1 - }, - new Customer - { - Name = "Jane", - Age = 24, - Id = 2 - } + new Customer { Name = "John", Age = 27, Id = 1 }, new Customer { Name = "Jane", Age = 24, Id = 2 } }; // Act @@ -1997,29 +1589,10 @@ public void When_the_subject_contains_more_items_than_expected_it_should_throw() // Arrange var subject = new List { - new Customer - { - Name = "John", - Age = 27, - Id = 1 - }, - new Customer - { - Name = "Jane", - Age = 24, - Id = 2 - } + new Customer { Name = "John", Age = 27, Id = 1 }, new Customer { Name = "Jane", Age = 24, Id = 2 } }; - var expectation = new List - { - new Customer - { - Name = "John", - Age = 27, - Id = 1 - } - }; + var expectation = new List { new Customer { Name = "John", Age = 27, Id = 1 } }; // Act Action action = @@ -2037,46 +1610,16 @@ public void When_the_subject_contains_same_number_of_items_and_both_contain_dupl // Arrange var subject = new List { - new Customer - { - Name = "John", - Age = 27, - Id = 1 - }, - new Customer - { - Name = "John", - Age = 27, - Id = 1 - }, - new Customer - { - Name = "Jane", - Age = 24, - Id = 2 - } + new Customer { Name = "John", Age = 27, Id = 1 }, + new Customer { Name = "John", Age = 27, Id = 1 }, + new Customer { Name = "Jane", Age = 24, Id = 2 } }; var expectation = new List { - new Customer - { - Name = "Jane", - Age = 24, - Id = 2 - }, - new Customer - { - Name = "John", - Age = 27, - Id = 1 - }, - new Customer - { - Name = "John", - Age = 27, - Id = 1 - } + new Customer { Name = "Jane", Age = 24, Id = 2 }, + new Customer { Name = "John", Age = 27, Id = 1 }, + new Customer { Name = "John", Age = 27, Id = 1 } }; // Act @@ -2093,34 +1636,12 @@ public void When_the_subject_contains_same_number_of_items_but_expectation_conta // Arrange var subject = new List { - new Customer - { - Name = "John", - Age = 27, - Id = 1 - }, - new Customer - { - Name = "Jane", - Age = 24, - Id = 2 - } + new Customer { Name = "John", Age = 27, Id = 1 }, new Customer { Name = "Jane", Age = 24, Id = 2 } }; var expectation = new List { - new Customer - { - Name = "John", - Age = 27, - Id = 1 - }, - new Customer - { - Name = "John", - Age = 27, - Id = 1 - } + new Customer { Name = "John", Age = 27, Id = 1 }, new Customer { Name = "John", Age = 27, Id = 1 } }; // Act @@ -2139,34 +1660,12 @@ public void When_the_subject_contains_same_number_of_items_but_subject_contains_ // Arrange var subject = new List { - new Customer - { - Name = "John", - Age = 27, - Id = 1 - }, - new Customer - { - Name = "John", - Age = 27, - Id = 1 - } + new Customer { Name = "John", Age = 27, Id = 1 }, new Customer { Name = "John", Age = 27, Id = 1 } }; var expectation = new List { - new Customer - { - Name = "John", - Age = 27, - Id = 1 - }, - new Customer - { - Name = "Jane", - Age = 24, - Id = 2 - } + new Customer { Name = "John", Age = 27, Id = 1 }, new Customer { Name = "Jane", Age = 24, Id = 2 } }; // Act @@ -2184,27 +1683,9 @@ public void When_two_collections_have_nested_members_of_the_contained_equivalent_but_not_equal_it_should_not_throw() { // Arrange - var list1 = new[] - { - new - { - Nested = new ClassWithOnlyAProperty - { - Value = 1 - } - } - }; + var list1 = new[] { new { Nested = new ClassWithOnlyAProperty { Value = 1 } } }; - var list2 = new[] - { - new - { - Nested = new - { - Value = 1 - } - } - }; + var list2 = new[] { new { Nested = new { Value = 1 } } }; // Act Action act = () => list1.Should().BeEquivalentTo(list2, opts => opts); @@ -2235,19 +1716,12 @@ public void When_two_equivalent_dictionaries_are_compared_directly_as_if_it_is_a_collection_it_should_succeed() { // Arrange - var result = new Dictionary - { - ["C"] = null, - ["B"] = 0, - ["A"] = 0 - }; + var result = new Dictionary { ["C"] = null, ["B"] = 0, ["A"] = 0 }; // Act Action act = () => result.Should().BeEquivalentTo(new Dictionary { - ["A"] = 0, - ["B"] = 0, - ["C"] = null + ["A"] = 0, ["B"] = 0, ["C"] = null }); // Assert @@ -2258,20 +1732,10 @@ public void public void When_two_equivalent_dictionaries_are_compared_directly_it_should_succeed() { // Arrange - var result = new Dictionary - { - ["C"] = 0, - ["B"] = 0, - ["A"] = 0 - }; + var result = new Dictionary { ["C"] = 0, ["B"] = 0, ["A"] = 0 }; // Act - Action act = () => result.Should().BeEquivalentTo(new Dictionary - { - ["A"] = 0, - ["B"] = 0, - ["C"] = 0 - }); + Action act = () => result.Should().BeEquivalentTo(new Dictionary { ["A"] = 0, ["B"] = 0, ["C"] = 0 }); // Assert act.Should().NotThrow(); @@ -2283,34 +1747,12 @@ public void When_two_lists_dont_contain_the_same_structural_equal_objects_it_sho // Arrange var subject = new List { - new Customer - { - Name = "John", - Age = 27, - Id = 1 - }, - new Customer - { - Name = "Jane", - Age = 24, - Id = 2 - } + new Customer { Name = "John", Age = 27, Id = 1 }, new Customer { Name = "Jane", Age = 24, Id = 2 } }; var expectation = new List { - new Customer - { - Name = "John", - Age = 27, - Id = 1 - }, - new Customer - { - Name = "Jane", - Age = 30, - Id = 2 - } + new Customer { Name = "John", Age = 27, Id = 1 }, new Customer { Name = "Jane", Age = 30, Id = 2 } }; // Act @@ -2328,32 +1770,12 @@ public void When_two_lists_only_differ_in_excluded_properties_it_should_not_thro // Arrange var subject = new List { - new Customer - { - Name = "John", - Age = 27, - Id = 1 - }, - new Customer - { - Name = "Jane", - Age = 24, - Id = 2 - } + new Customer { Name = "John", Age = 27, Id = 1 }, new Customer { Name = "Jane", Age = 24, Id = 2 } }; var expectation = new List { - new CustomerDto - { - Name = "John", - Age = 27 - }, - new CustomerDto - { - Name = "Jane", - Age = 30 - } + new CustomerDto { Name = "John", Age = 27 }, new CustomerDto { Name = "Jane", Age = 30 } }; // Act @@ -2403,7 +1825,7 @@ public void When_two_multi_dimensional_arrays_are_not_equivalent_it_should_throw var expectation = new[,] { - { 1, 2, 4 }, + { 1, 2, 4 }, { 4, -5, 6 } }; @@ -2449,21 +1871,9 @@ public void When_two_multi_dimensional_arrays_are_empty_they_should_be_equivalen public void When_two_nested_dictionaries_contain_null_values_it_should_not_crash() { // Arrange - var projection = new - { - ReferencedEquipment = new Dictionary - { - [1] = null - } - }; + var projection = new { ReferencedEquipment = new Dictionary { [1] = null } }; - var persistedProjection = new - { - ReferencedEquipment = new Dictionary - { - [1] = null - } - }; + var persistedProjection = new { ReferencedEquipment = new Dictionary { [1] = null } }; // Act Action act = () => persistedProjection.Should().BeEquivalentTo(projection); @@ -2496,21 +1906,9 @@ public void When_two_nested_dictionaries_contain_null_values_it_should_not_crash public void When_two_nested_dictionaries_do_not_match_it_should_throw() { // Arrange - var projection = new - { - ReferencedEquipment = new Dictionary - { - [1] = "Bla1" - } - }; + var projection = new { ReferencedEquipment = new Dictionary { [1] = "Bla1" } }; - var persistedProjection = new - { - ReferencedEquipment = new Dictionary - { - [1] = "Bla2" - } - }; + var persistedProjection = new { ReferencedEquipment = new Dictionary { [1] = "Bla2" } }; // Act Action act = () => persistedProjection.Should().BeEquivalentTo(projection); @@ -2526,34 +1924,12 @@ public void When_two_ordered_lists_are_structurally_equivalent_it_should_succeed // Arrange var subject = new List { - new Customer - { - Name = "John", - Age = 27, - Id = 1 - }, - new Customer - { - Name = "Jane", - Age = 24, - Id = 2 - } + new Customer { Name = "John", Age = 27, Id = 1 }, new Customer { Name = "Jane", Age = 24, Id = 2 } }; var expectation = new List { - new Customer - { - Name = "John", - Age = 27, - Id = 1 - }, - new Customer - { - Name = "Jane", - Age = 24, - Id = 2 - } + new Customer { Name = "John", Age = 27, Id = 1 }, new Customer { Name = "Jane", Age = 24, Id = 2 } }; // Act @@ -2570,34 +1946,12 @@ public void When_two_unordered_lists_are_structurally_equivalent_and_order_is_st // Arrange var subject = new[] { - new Customer - { - Name = "John", - Age = 27, - Id = 1 - }, - new Customer - { - Name = "Jane", - Age = 24, - Id = 2 - } + new Customer { Name = "John", Age = 27, Id = 1 }, new Customer { Name = "Jane", Age = 24, Id = 2 } }; var expectation = new Collection { - new Customer - { - Name = "Jane", - Age = 24, - Id = 2 - }, - new Customer - { - Name = "John", - Age = 27, - Id = 1 - } + new Customer { Name = "Jane", Age = 24, Id = 2 }, new Customer { Name = "John", Age = 27, Id = 1 } }; // Act @@ -2615,34 +1969,12 @@ public void When_two_unordered_lists_are_structurally_equivalent_and_order_was_r // Arrange var subject = new[] { - new Customer - { - Name = "John", - Age = 27, - Id = 1 - }, - new Customer - { - Name = "Jane", - Age = 24, - Id = 2 - } + new Customer { Name = "John", Age = 27, Id = 1 }, new Customer { Name = "Jane", Age = 24, Id = 2 } }; var expectation = new Collection { - new Customer - { - Name = "Jane", - Age = 24, - Id = 2 - }, - new Customer - { - Name = "John", - Age = 27, - Id = 1 - } + new Customer { Name = "Jane", Age = 24, Id = 2 }, new Customer { Name = "John", Age = 27, Id = 1 } }; // Act @@ -2665,34 +1997,12 @@ public void When_two_unordered_lists_are_structurally_equivalent_and_order_was_r // Arrange var subject = new[] { - new Customer - { - Name = "John", - Age = 27, - Id = 1 - }, - new Customer - { - Name = "Jane", - Age = 24, - Id = 2 - } + new Customer { Name = "John", Age = 27, Id = 1 }, new Customer { Name = "Jane", Age = 24, Id = 2 } }; var expectation = new Collection { - new Customer - { - Name = "Jane", - Age = 24, - Id = 2 - }, - new Customer - { - Name = "John", - Age = 27, - Id = 1 - } + new Customer { Name = "Jane", Age = 24, Id = 2 }, new Customer { Name = "John", Age = 27, Id = 1 } }; // Act @@ -2709,34 +2019,12 @@ public void When_two_unordered_lists_are_structurally_equivalent_it_should_succe // Arrange var subject = new[] { - new Customer - { - Name = "John", - Age = 27, - Id = 1 - }, - new Customer - { - Name = "Jane", - Age = 24, - Id = 2 - } + new Customer { Name = "John", Age = 27, Id = 1 }, new Customer { Name = "Jane", Age = 24, Id = 2 } }; var expectation = new Collection { - new Customer - { - Name = "Jane", - Age = 24, - Id = 2 - }, - new Customer - { - Name = "John", - Age = 27, - Id = 1 - } + new Customer { Name = "Jane", Age = 24, Id = 2 }, new Customer { Name = "John", Age = 27, Id = 1 } }; // Act @@ -2791,7 +2079,8 @@ 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) + public void When_two_unordered_lists_contain_empty_objects_they_should_still_be_structurally_equivalent(TActual[] actual, TExpected[] expected) { // Act Action act = () => actual.Should().BeEquivalentTo(expected); @@ -2807,7 +2096,8 @@ public void When_an_exception_is_thrown_during_data_access_the_stack_trace_conta var genericCollectionA = new List() { new ExceptionThrowingClass() }; var genericCollectionB = new List() { new ExceptionThrowingClass() }; - var expectedTargetSite = typeof(ExceptionThrowingClass).GetProperty(nameof(ExceptionThrowingClass.ExceptionThrowingProperty)).GetMethod; + var expectedTargetSite = typeof(ExceptionThrowingClass) + .GetProperty(nameof(ExceptionThrowingClass.ExceptionThrowingProperty)).GetMethod; // Act Action act = () => genericCollectionA.Should().BeEquivalentTo(genericCollectionB); @@ -2820,15 +2110,12 @@ public static IEnumerable ArrayTestData() { var arrays = new object[] { - new int?[] { null, 1 }, - new int?[] { 1, null }, - new object[] { null, 1 }, - new object[] { 1, null } + new int?[] { null, 1 }, new int?[] { 1, null }, new object[] { null, 1 }, new object[] { 1, null } }; return from x in arrays - from y in arrays - select new object[] { x, y }; + from y in arrays + select new object[] { x, y }; } [Fact] @@ -2902,5 +2189,27 @@ private class ClassWithLotsOfProperties public string Value12 { get; set; } } + + private class LogbookEntryProjection + { + public virtual LogbookCode Logbook { get; set; } + + public virtual ICollection LogbookRelations { get; set; } + } + + private class LogbookRelation + { + public virtual LogbookCode Logbook { get; set; } + } + + private class LogbookCode + { + public LogbookCode(string key) + { + Key = key; + } + + public string Key { get; protected set; } + } } } diff --git a/Tests/FluentAssertions.Equivalency.Specs/CyclicReferencesSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/CyclicReferencesSpecs.cs new file mode 100644 index 0000000000..6bab1a6d64 --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/CyclicReferencesSpecs.cs @@ -0,0 +1,333 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Equivalency.Specs +{ + public class CyclicReferencesSpecs + { + [Fact] + public void When_validating_nested_properties_that_have_cyclic_references_it_should_throw() + { + // Arrange + var cyclicRoot = new CyclicRoot { Text = "Root" }; + + cyclicRoot.Level = new CyclicLevel1 { Text = "Level1", Root = cyclicRoot }; + + var cyclicRootDto = new CyclicRootDto { Text = "Root" }; + + cyclicRootDto.Level = new CyclicLevel1Dto { Text = "Level1", Root = cyclicRootDto }; + + // Act + Action act = () => cyclicRoot.Should().BeEquivalentTo(cyclicRootDto); + + // Assert + act + .Should().Throw() + .WithMessage("Expected property cyclicRoot.Level.Root to be*but it contains a cyclic reference*"); + } + + [Fact] + public void When_validating_nested_properties_and_ignoring_cyclic_references_it_should_succeed() + { + // Arrange + var cyclicRoot = new CyclicRoot { Text = "Root" }; + cyclicRoot.Level = new CyclicLevel1 { Text = "Level1", Root = cyclicRoot }; + + var cyclicRootDto = new CyclicRootDto { Text = "Root" }; + cyclicRootDto.Level = new CyclicLevel1Dto { Text = "Level1", Root = cyclicRootDto }; + + // Act + Action act = () => + cyclicRoot.Should().BeEquivalentTo(cyclicRootDto, options => options.IgnoringCyclicReferences()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_two_cyclic_graphs_are_equivalent_when_ignoring_cycle_references_it_should_succeed() + { + // Arrange + var actual = new Parent(); + actual.Child1 = new Child(actual, 1); + actual.Child2 = new Child(actual); + + var expected = new Parent(); + expected.Child1 = new Child(expected); + expected.Child2 = new Child(expected); + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected, x => x + .Excluding(y => y.Child1) + .IgnoringCyclicReferences()); + + // Assert + act.Should().NotThrow(); + } + + public class Parent + { + public Child Child1 { get; set; } + + public Child Child2 { get; set; } + } + + public class Child + { + public Child(Parent parent, int stuff = 0) + { + Parent = parent; + Stuff = stuff; + } + + public Parent Parent { get; set; } + + public int Stuff { get; set; } + } + + [Fact] + public void When_validating_nested_properties_that_are_null_it_should_not_throw_on_cyclic_references() + { + // Arrange + var actual = new CyclicRoot { Text = null }; + + actual.Level = new CyclicLevel1 { Text = null, Root = null }; + + var expectation = new CyclicRootDto { Text = null }; + + expectation.Level = new CyclicLevel1Dto { Text = null, Root = null }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_the_graph_contains_the_same_value_object_it_should_not_be_treated_as_a_cyclic_reference() + { + // Arrange + var actual = new CyclicRootWithValueObject { Object = new ValueObject("MyValue") }; + + actual.Level = new CyclicLevelWithValueObject { Object = new ValueObject("MyValue"), Root = null }; + + var expectation = new CyclicRootWithValueObject { Object = new ValueObject("MyValue") }; + + expectation.Level = new CyclicLevelWithValueObject { Object = new ValueObject("MyValue"), Root = null }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_types_with_infinite_object_graphs_are_equivalent_it_should_not_overflow_the_stack() + { + // Arrange + var recursiveClass1 = new ClassWithInfinitelyRecursiveProperty(); + var recursiveClass2 = new ClassWithInfinitelyRecursiveProperty(); + + // Act + Action act = + () => recursiveClass1.Should().BeEquivalentTo(recursiveClass2); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void + When_asserting_equivalence_on_objects_needing_high_recursion_depth_and_disabling_recursion_depth_limit_it_should_recurse_to_completion() + { + // Arrange + var recursiveClass1 = new ClassWithFiniteRecursiveProperty(15); + var recursiveClass2 = new ClassWithFiniteRecursiveProperty(15); + + // Act + Action act = + () => recursiveClass1.Should().BeEquivalentTo(recursiveClass2, + options => options.AllowingInfiniteRecursion()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_injecting_a_null_config_to_BeEquivalentTo_it_should_throw() + { + // Arrange + var recursiveClass1 = new ClassWithFiniteRecursiveProperty(15); + var recursiveClass2 = new ClassWithFiniteRecursiveProperty(15); + + // Act + Action act = () => recursiveClass1.Should().BeEquivalentTo(recursiveClass2, config: null); + + // Assert + act.Should().ThrowExactly() + .WithParameterName("config"); + } + + [Fact] + public void + When_asserting_inequivalence_on_objects_needing_high_recursion_depth_and_disabling_recursion_depth_limit_it_should_recurse_to_completion() + { + // Arrange + var recursiveClass1 = new ClassWithFiniteRecursiveProperty(15); + var recursiveClass2 = new ClassWithFiniteRecursiveProperty(16); + + // Act + Action act = + () => recursiveClass1.Should().NotBeEquivalentTo(recursiveClass2, + options => options.AllowingInfiniteRecursion()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_injecting_a_null_config_to_NotBeEquivalentTo_it_should_throw() + { + // Arrange + var recursiveClass1 = new ClassWithFiniteRecursiveProperty(15); + var recursiveClass2 = new ClassWithFiniteRecursiveProperty(16); + + // Act + Action act = () => recursiveClass1.Should().NotBeEquivalentTo(recursiveClass2, config: null); + + // Assert + act.Should().ThrowExactly() + .WithParameterName("config"); + } + + [Fact] + public void When_an_enumerable_collection_returns_itself_it_should_detect_the_cyclic_reference() + { + // Act + var instance1 = new SelfReturningEnumerable(); + var instance2 = new SelfReturningEnumerable(); + var actual = new List { instance1, instance2 }; + + // Assert + Action act = () => actual.Should().BeEquivalentTo( + new[] { new SelfReturningEnumerable(), new SelfReturningEnumerable() }, + "we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw().WithMessage("*we want to test the failure message*cyclic reference*"); + } + + public class SelfReturningEnumerable : IEnumerable + { + public IEnumerator GetEnumerator() + { + yield return this; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + internal class LogbookEntryProjection + { + public virtual LogbookCode Logbook { get; set; } + + public virtual ICollection LogbookRelations { get; set; } + } + + internal class LogbookRelation + { + public virtual LogbookCode Logbook { get; set; } + } + + internal class LogbookCode + { + public LogbookCode(string key) + { + Key = key; + } + + public string Key { get; protected set; } + } + + [Fact] + public void When_the_root_object_is_referenced_from_a_nested_object_it_should_treat_it_as_a_cyclic_reference() + { + // Arrange + var company1 = new MyCompany { Name = "Company" }; + var user1 = new MyUser { Name = "User", Company = company1 }; + 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 }; + var logo2 = new MyCompanyLogo { Url = "blank", Company = company2, CreatedBy = user2 }; + company2.Logo = logo2; + + // Act + Action action = () => company1.Should().BeEquivalentTo(company2, o => o.IgnoringCyclicReferences()); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void Allow_ignoring_cyclic_references_in_value_types_compared_by_members() + { + // Arrange + var expectation = new ValueTypeCircularDependency() { Title = "First" }; + + var second = new ValueTypeCircularDependency() { Title = "Second", Previous = expectation }; + + expectation.Next = second; + + var subject = new ValueTypeCircularDependency() { Title = "First" }; + + var secondCopy = new ValueTypeCircularDependency() { Title = "SecondDifferent", Previous = subject }; + + subject.Next = secondCopy; + + // Act + Action act = () => subject.Should() + .BeEquivalentTo(expectation, opt => opt + .ComparingByMembers() + .IgnoringCyclicReferences()); + + // Assert + act.Should().Throw() + .WithMessage("*subject.Next.Title*Second*SecondDifferent*") + .Which.Message.Should().NotContain("maximum recursion depth was reached"); + } + + public class ValueTypeCircularDependency + { + public string Title { get; set; } + + public ValueTypeCircularDependency Previous { get; set; } + + public ValueTypeCircularDependency Next { get; set; } + + public override bool Equals(object obj) + { + if (ReferenceEquals(this, obj)) + { + return true; + } + + return (obj is ValueTypeCircularDependency baseObj) && baseObj.Title == Title; + } + + public override int GetHashCode() + { + return Title.GetHashCode(); + } + } + } +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/DataColumnSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/DataColumnSpecs.cs new file mode 100644 index 0000000000..f8ab700ca6 --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/DataColumnSpecs.cs @@ -0,0 +1,708 @@ +using System; +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 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) => 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 dataTable1 = dataSet1.TypedDataTable1; + 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) => 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)); + } + } +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/DataEquivalencySpecs.DataColumn.cs b/Tests/FluentAssertions.Equivalency.Specs/DataEquivalencySpecs.DataColumn.cs deleted file mode 100644 index 808a893bbd..0000000000 --- a/Tests/FluentAssertions.Equivalency.Specs/DataEquivalencySpecs.DataColumn.cs +++ /dev/null @@ -1,714 +0,0 @@ -using System; -using System.Data; - -using Xunit; -using Xunit.Sdk; - -namespace FluentAssertions.Specs -{ - /// - /// DataColumnEquivalency specs. - /// - public partial class DataEquivalencySpecs - { - public class DataColumnEquivalencySpecs : DataEquivalencySpecs - { - [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 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) => 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 dataTable1 = dataSet1.TypedDataTable1; - 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) => 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)); - } - } - } -} diff --git a/Tests/FluentAssertions.Equivalency.Specs/DataEquivalencySpecs.DataRelation.cs b/Tests/FluentAssertions.Equivalency.Specs/DataEquivalencySpecs.DataRelation.cs deleted file mode 100644 index 1e2eeeab20..0000000000 --- a/Tests/FluentAssertions.Equivalency.Specs/DataEquivalencySpecs.DataRelation.cs +++ /dev/null @@ -1,262 +0,0 @@ -using System; -using System.Data; - -using Xunit; -using Xunit.Sdk; - -namespace FluentAssertions.Specs -{ - /// - /// DataRelationEquivalency specs. - /// - public partial class DataEquivalencySpecs - { - public class DataRelationEquivalencySpecs : DataEquivalencySpecs - { - [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) => 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) => 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) => dataRelation.ParentColumns) - .ExcludingRelated((DataRelation dataRelation) => dataRelation.ParentKeyConstraint) - .ExcludingRelated((DataRelation 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) => dataRelation.ChildColumns) - .ExcludingRelated((DataRelation dataRelation) => dataRelation.ParentKeyConstraint) - .ExcludingRelated((DataRelation dataRelation) => dataRelation.ChildKeyConstraint)); - } - } - } -} diff --git a/Tests/FluentAssertions.Equivalency.Specs/DataEquivalencySpecs.DataSet.Typed.cs b/Tests/FluentAssertions.Equivalency.Specs/DataEquivalencySpecs.DataSet.Typed.cs deleted file mode 100644 index 312b3be7fe..0000000000 --- a/Tests/FluentAssertions.Equivalency.Specs/DataEquivalencySpecs.DataSet.Typed.cs +++ /dev/null @@ -1,550 +0,0 @@ -using System; -using System.Data; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; - -using Xunit; -using Xunit.Sdk; - -namespace FluentAssertions.Specs -{ - /// - /// DataSetEquivalency specs for typed data sets. - /// - public partial class DataEquivalencySpecs - { - public class TypedDataSetEquivalencySpecs : DataEquivalencySpecs - { - [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.ExcludingTables("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).ExcludingTables("TypedDataTable1")); - } - - [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)); - } - - [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) => 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)); - } - - [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) => dataTable.ParentRelations) - .ExcludingRelated((DataTable 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/DataEquivalencySpecs.DataSet.cs b/Tests/FluentAssertions.Equivalency.Specs/DataEquivalencySpecs.DataSet.cs deleted file mode 100644 index 6db86851cf..0000000000 --- a/Tests/FluentAssertions.Equivalency.Specs/DataEquivalencySpecs.DataSet.cs +++ /dev/null @@ -1,620 +0,0 @@ -using System; -using System.Data; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; - -using Xunit; -using Xunit.Sdk; - -namespace FluentAssertions.Specs -{ - /// - /// DataSetEquivalency specs. - /// - public partial class DataEquivalencySpecs - { - public class DataSetEquivalencySpecs : DataEquivalencySpecs - { - [Fact] - public void When_DataSets_are_identical_it_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_DataSets_are_both_null_it_should_succeed() - { - // Act & Assert - ((DataSet)null).Should().BeEquivalentTo(null); - } - - [Fact] - public void When_DataSet_is_null_and_isnt_expected_to_be_it_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_DataSet_is_expected_to_be_null_and_isnt_it_should_fail() - { - // Arrange - var typedDataSet = CreateDummyDataSet(); - - var dataSet = typedDataSet.ToUntypedDataSet(); - - // 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 typedDataSet = CreateDummyDataSet(); - - var dataSet = typedDataSet.ToUntypedDataSet(); - - var dataSetOfMismatchedType = new TypedDataSetSubclass(typedDataSet); - - // 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 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_DataSetName_does_not_match_and_property_is_not_excluded_it_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(); - } - - [Fact] - public void When_DataSetName_does_not_match_and_property_is_excluded_it_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_CaseSensitive_does_not_match_and_property_is_not_excluded_it_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(); - } - - [Fact] - public void When_CaseSensitive_does_not_match_and_property_is_excluded_it_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_EnforceConstraints_does_not_match_and_property_is_not_excluded_it_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(); - } - - [Fact] - public void When_EnforceConstraints_does_not_match_and_property_is_excluded_it_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_HasErrors_does_not_match_and_property_is_not_excluded_it_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_HasErrors_does_not_match_and_property_is_excluded_it_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_Locale_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - 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(); - } - - [Fact] - public void When_Locale_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - 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_Namespace_does_not_match_and_property_is_not_excluded_it_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(); - } - - [Fact] - public void When_Namespace_does_not_match_and_property_is_excluded_it_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_Prefix_does_not_match_and_property_is_not_excluded_it_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(); - } - - [Fact] - public void When_Prefix_does_not_match_and_property_is_excluded_it_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)); - } - - [Fact] - public void When_RemotingFormat_does_not_match_and_property_is_not_excluded_it_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(); - } - - [Fact] - public void When_RemotingFormat_does_not_match_and_property_is_excluded_it_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) => dataTable.RemotingFormat)); - } - - [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); - - ApplyChange(typedDataSet2.ExtendedProperties, changeType); - - var dataSet1 = typedDataSet1.ToUntypedDataSet(); - var dataSet2 = typedDataSet2.ToUntypedDataSet(); - - // 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 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)); - } - - [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 - 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(); - } - - [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 - 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) => dataTable.Constraints) - .ExcludingRelated((DataTable dataTable) => dataTable.ParentRelations) - .ExcludingRelated((DataTable dataTable) => dataTable.ChildRelations) - .ExcludingRelated((DataColumn dataColumn) => dataColumn.Unique)); - } - - [Fact] - public void When_Tables_are_the_same_but_in_a_different_order_it_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_Tables_count_does_not_match_it_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_Tables_count_matches_but_tables_are_different_it_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(); - } - - [Fact] - public void When_Tables_contain_different_data_it_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(); - } - } - } -} diff --git a/Tests/FluentAssertions.Equivalency.Specs/DataRelationSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/DataRelationSpecs.cs new file mode 100644 index 0000000000..71c79a7f1e --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/DataRelationSpecs.cs @@ -0,0 +1,258 @@ +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) => 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) => 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) => dataRelation.ParentColumns) + .ExcludingRelated((DataRelation dataRelation) => dataRelation.ParentKeyConstraint) + .ExcludingRelated((DataRelation 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) => dataRelation.ChildColumns) + .ExcludingRelated((DataRelation dataRelation) => dataRelation.ParentKeyConstraint) + .ExcludingRelated((DataRelation dataRelation) => dataRelation.ChildKeyConstraint)); + } + } +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/DataEquivalencySpecs.DataRow.cs b/Tests/FluentAssertions.Equivalency.Specs/DataRowSpecs.cs similarity index 98% rename from Tests/FluentAssertions.Equivalency.Specs/DataEquivalencySpecs.DataRow.cs rename to Tests/FluentAssertions.Equivalency.Specs/DataRowSpecs.cs index 1138b145b9..3e8b9ec947 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/DataEquivalencySpecs.DataRow.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/DataRowSpecs.cs @@ -1,17 +1,11 @@ using System; using System.Data; - using Xunit; using Xunit.Sdk; -namespace FluentAssertions.Specs +namespace FluentAssertions.Equivalency.Specs { - /// - /// DataRowEquivalency specs. - /// - public partial class DataEquivalencySpecs - { - public class DataRowEquivalencySpecs + public class DataRowSpecs : DataSpecs { [Fact] public void When_DataRows_are_identical_it_should_succeed() @@ -336,4 +330,3 @@ public void When_data_does_not_match_and_column_is_excluded_it_should_succeed() } } } -} diff --git a/Tests/FluentAssertions.Equivalency.Specs/DataSetSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/DataSetSpecs.cs new file mode 100644 index 0000000000..18b2a93c58 --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/DataSetSpecs.cs @@ -0,0 +1,614 @@ +using System; +using System.Data; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Equivalency.Specs +{ + public class DataSetSpecs : DataSpecs + { + [Fact] + public void When_DataSets_are_identical_it_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_DataSets_are_both_null_it_should_succeed() + { + // Act & Assert + ((DataSet)null).Should().BeEquivalentTo(null); + } + + [Fact] + public void When_DataSet_is_null_and_isnt_expected_to_be_it_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_DataSet_is_expected_to_be_null_and_isnt_it_should_fail() + { + // Arrange + var typedDataSet = CreateDummyDataSet(); + + var dataSet = typedDataSet.ToUntypedDataSet(); + + // 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 typedDataSet = CreateDummyDataSet(); + + var dataSet = typedDataSet.ToUntypedDataSet(); + + var dataSetOfMismatchedType = new TypedDataSetSubclass(typedDataSet); + + // 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 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_DataSetName_does_not_match_and_property_is_not_excluded_it_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(); + } + + [Fact] + public void When_DataSetName_does_not_match_and_property_is_excluded_it_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_CaseSensitive_does_not_match_and_property_is_not_excluded_it_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(); + } + + [Fact] + public void When_CaseSensitive_does_not_match_and_property_is_excluded_it_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_EnforceConstraints_does_not_match_and_property_is_not_excluded_it_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(); + } + + [Fact] + public void When_EnforceConstraints_does_not_match_and_property_is_excluded_it_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_HasErrors_does_not_match_and_property_is_not_excluded_it_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_HasErrors_does_not_match_and_property_is_excluded_it_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_Locale_does_not_match_and_property_is_not_excluded_it_should_fail() + { + // Arrange + var typedDataSet1 = CreateDummyDataSet(); + + var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); + + 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(); + } + + [Fact] + public void When_Locale_does_not_match_and_property_is_excluded_it_should_succeed() + { + // Arrange + var typedDataSet1 = CreateDummyDataSet(); + + var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); + + 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_Namespace_does_not_match_and_property_is_not_excluded_it_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(); + } + + [Fact] + public void When_Namespace_does_not_match_and_property_is_excluded_it_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_Prefix_does_not_match_and_property_is_not_excluded_it_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(); + } + + [Fact] + public void When_Prefix_does_not_match_and_property_is_excluded_it_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)); + } + + [Fact] + public void When_RemotingFormat_does_not_match_and_property_is_not_excluded_it_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(); + } + + [Fact] + public void When_RemotingFormat_does_not_match_and_property_is_excluded_it_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) => dataTable.RemotingFormat)); + } + + [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); + + ApplyChange(typedDataSet2.ExtendedProperties, changeType); + + var dataSet1 = typedDataSet1.ToUntypedDataSet(); + var dataSet2 = typedDataSet2.ToUntypedDataSet(); + + // 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 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)); + } + + [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 + 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(); + } + + [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 + 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) => dataTable.Constraints) + .ExcludingRelated((DataTable dataTable) => dataTable.ParentRelations) + .ExcludingRelated((DataTable dataTable) => dataTable.ChildRelations) + .ExcludingRelated((DataColumn dataColumn) => dataColumn.Unique)); + } + + [Fact] + public void When_Tables_are_the_same_but_in_a_different_order_it_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_Tables_count_does_not_match_it_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_Tables_count_matches_but_tables_are_different_it_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(); + } + + [Fact] + public void When_Tables_contain_different_data_it_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(); + } + } +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/DataEquivalencySpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/DataSpecs.cs similarity index 96% rename from Tests/FluentAssertions.Equivalency.Specs/DataEquivalencySpecs.cs rename to Tests/FluentAssertions.Equivalency.Specs/DataSpecs.cs index 4a79afd091..cb40f28b27 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/DataEquivalencySpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/DataSpecs.cs @@ -5,12 +5,12 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; -namespace FluentAssertions.Specs +namespace FluentAssertions.Equivalency.Specs { /// /// Class containing specs related to System.Data, including many common declarations and methods. /// - public partial class DataEquivalencySpecs + 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. @@ -365,12 +365,12 @@ public TypedDataSetSubclass(TypedDataSet copyFrom, bool swapTableOrder = false, SchemaSerializationMode = copyFrom.SchemaSerializationMode; CopyTable( - from: copyFrom.TypedDataTable1, + @from: copyFrom.TypedDataTable1, to: TypedDataTable1, randomizeRowOrder); CopyTable( - from: copyFrom.TypedDataTable2, + @from: copyFrom.TypedDataTable2, to: TypedDataTable2, randomizeRowOrder); @@ -433,7 +433,7 @@ public TypedDataSetSubclass(TypedDataSet copyFrom, bool swapTableOrder = false, private static readonly Random Random = new Random(); - private static TDataSet CreateDummyDataSet(bool identicalTables = false, bool includeDummyData = true, bool includeRelation = true) + internal static TDataSet CreateDummyDataSet(bool identicalTables = false, bool includeDummyData = true, bool includeRelation = true) where TDataSet : TypedDataSet, new() { var ret = new TDataSet(); @@ -495,7 +495,7 @@ private static TDataSet CreateDummyDataSet(bool identicalTables = fals return ret; } - private static void AddRelation(TypedDataSet dataSet) + protected static void AddRelation(TypedDataSet dataSet) { var relation = new DataRelation("TestRelation", dataSet.TypedDataTable1.RowIDColumn, dataSet.TypedDataTable2.ForeignRowIDColumn); @@ -582,7 +582,7 @@ public static IEnumerable AllChangeTypesWithAcceptChangesValues (changeType, acceptChanges) => new object[] { changeType, acceptChanges }); [SuppressMessage("Style", "IDE0010:Add missing cases", Justification = "Every enum value is covered")] - private static void ApplyChange(DataColumnCollection columns, ChangeType changeType) + protected static void ApplyChange(DataColumnCollection columns, ChangeType changeType) { switch (changeType) { @@ -599,7 +599,7 @@ private static void ApplyChange(DataColumnCollection columns, ChangeType changeT } [SuppressMessage("Style", "IDE0010:Add missing cases", Justification = "Every enum value is covered")] - private static void ApplyChange(PropertyCollection extendedProperties, ChangeType changeType) + protected static void ApplyChange(PropertyCollection extendedProperties, ChangeType changeType) { switch (changeType) { @@ -616,7 +616,7 @@ private static void ApplyChange(PropertyCollection extendedProperties, ChangeTyp } [SuppressMessage("Style", "IDE0010:Add missing cases", Justification = "Every enum value is covered")] - private static void ApplyChange(ConstraintCollection constraints, DataColumn columnForNewConstraint, ChangeType changeType) + protected static void ApplyChange(ConstraintCollection constraints, DataColumn columnForNewConstraint, ChangeType changeType) { switch (changeType) { @@ -636,7 +636,7 @@ private static void ApplyChange(ConstraintCollection constraints, DataColumn col } [SuppressMessage("Style", "IDE0010:Add missing cases", Justification = "Every enum value is covered")] - private static void ApplyChange(DataRowCollection rows, DataTable dataTable, ChangeType changeType) + protected static void ApplyChange(DataRowCollection rows, DataTable dataTable, ChangeType changeType) { switch (changeType) { diff --git a/Tests/FluentAssertions.Equivalency.Specs/DataEquivalencySpecs.DataTable.cs b/Tests/FluentAssertions.Equivalency.Specs/DataTableSpecs.cs similarity index 99% rename from Tests/FluentAssertions.Equivalency.Specs/DataEquivalencySpecs.DataTable.cs rename to Tests/FluentAssertions.Equivalency.Specs/DataTableSpecs.cs index 2bfd47bcbc..546b661c1f 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/DataEquivalencySpecs.DataTable.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/DataTableSpecs.cs @@ -2,20 +2,13 @@ using System.Data; using System.Globalization; using System.Linq; - using FluentAssertions.Data; - using Xunit; using Xunit.Sdk; -namespace FluentAssertions.Specs +namespace FluentAssertions.Equivalency.Specs { - /// - /// DataTableEquivalency specs. - /// - public partial class DataEquivalencySpecs - { - public class DataTableEquivalencySpecs : DataEquivalencySpecs + public class DataTableSpecs : DataSpecs { [Fact] public void When_DataTables_are_identical_it_should_succeed() @@ -704,4 +697,3 @@ public void When_data_matches_in_different_order_and_RowMatchMode_is_PrimaryKey_ } } } -} diff --git a/Tests/FluentAssertions.Equivalency.Specs/DateTimePropertiesSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/DateTimePropertiesSpecs.cs new file mode 100644 index 0000000000..da7b74af4a --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/DateTimePropertiesSpecs.cs @@ -0,0 +1,154 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Equivalency.Specs +{ + public class DateTimePropertiesSpecs + { + [Fact] + public void When_two_properties_are_datetime_and_both_are_nullable_and_both_are_null_it_should_succeed() + { + // Arrange + var subject = + new { Time = (DateTime?)null }; + + var other = + new { Time = (DateTime?)null }; + + // Act + Action act = () => + subject.Should().BeEquivalentTo(other); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_two_properties_are_datetime_and_both_are_nullable_and_are_equal_it_should_succeed() + { + // Arrange + var subject = + new { Time = (DateTime?)new DateTime(2013, 12, 9, 15, 58, 0) }; + + var other = + new { Time = (DateTime?)new DateTime(2013, 12, 9, 15, 58, 0) }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(other); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void + When_two_properties_are_datetime_and_both_are_nullable_and_expectation_is_null_it_should_throw_and_state_the_difference() + { + // Arrange + var subject = + new { Time = (DateTime?)new DateTime(2013, 12, 9, 15, 58, 0) }; + + var other = + new { Time = (DateTime?)null }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(other); + + // Assert + act.Should().Throw().WithMessage( + "Expected*Time to be , but found <2013-12-09 15:58:00>.*"); + } + + [Fact] + public void + When_two_properties_are_datetime_and_both_are_nullable_and_subject_is_null_it_should_throw_and_state_the_difference() + { + // Arrange + var subject = + new { Time = (DateTime?)null }; + + var other = + new { Time = (DateTime?)new DateTime(2013, 12, 9, 15, 58, 0) }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(other); + + // Assert + act.Should().Throw().WithMessage( + "Expected*Time*to be <2013-12-09 15:58:00>, but found .*"); + } + + [Fact] + public void When_two_properties_are_datetime_and_expectation_is_nullable_and_are_equal_it_should_succeed() + { + // Arrange + var subject = + new { Time = new DateTime(2013, 12, 9, 15, 58, 0) }; + + var other = + new { Time = (DateTime?)new DateTime(2013, 12, 9, 15, 58, 0) }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(other); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void + When_two_properties_are_datetime_and_expectation_is_nullable_and_expectation_is_null_it_should_throw_and_state_the_difference() + { + // Arrange + var subject = + new { Time = new DateTime(2013, 12, 9, 15, 58, 0) }; + + var other = + new { Time = (DateTime?)null }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(other); + + // Assert + act.Should().Throw().WithMessage( + "Expected*Time*to be , but found <2013-12-09 15:58:00>.*"); + } + + [Fact] + public void When_two_properties_are_datetime_and_subject_is_nullable_and_are_equal_it_should_succeed() + { + // Arrange + var subject = + new { Time = (DateTime?)new DateTime(2013, 12, 9, 15, 58, 0) }; + + var other = + new { Time = new DateTime(2013, 12, 9, 15, 58, 0) }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(other); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void + When_two_properties_are_datetime_and_subject_is_nullable_and_subject_is_null_it_should_throw_and_state_the_difference() + { + // Arrange + var subject = + new { Time = (DateTime?)null }; + + var other = + new { Time = new DateTime(2013, 12, 9, 15, 58, 0) }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(other); + + // Assert + act.Should().Throw().WithMessage( + "Expected*Time*to be <2013-12-09 15:58:00>, but found .*"); + } + } +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/DictionaryEquivalencySpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/DictionarySpecs.cs similarity index 99% rename from Tests/FluentAssertions.Equivalency.Specs/DictionaryEquivalencySpecs.cs rename to Tests/FluentAssertions.Equivalency.Specs/DictionarySpecs.cs index 4d5dbaeb1b..a7fa1e1f9a 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/DictionaryEquivalencySpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/DictionarySpecs.cs @@ -9,9 +9,9 @@ using Xunit; using Xunit.Sdk; -namespace FluentAssertions.Specs.Equivalency +namespace FluentAssertions.Equivalency.Specs { - public class DictionaryEquivalencySpecs + public class DictionarySpecs { private class NonGenericDictionary : IDictionary { @@ -928,7 +928,7 @@ public void When_subject_dictionary_with_class_keys_asserted_to_be_equivalent_an // Assert action.Should().Throw() - .WithMessage("Expected dictionary2 to be a dictionary or collection of key-value pairs that is keyed to type FluentAssertions.Specs.Equivalency.DictionaryEquivalencySpecs+SomeBaseKeyClass.*"); + .WithMessage("Expected dictionary2 to be a dictionary or collection of key-value pairs that is keyed to type FluentAssertions.Equivalency.Specs.DictionarySpecs+SomeBaseKeyClass.*"); } [Fact] diff --git a/Tests/FluentAssertions.Equivalency.Specs/EnumSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/EnumSpecs.cs new file mode 100644 index 0000000000..4a5c49cf15 --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/EnumSpecs.cs @@ -0,0 +1,313 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Equivalency.Specs +{ + public class EnumSpecs + { + [Fact] + public void When_asserting_the_same_enum_member_is_equivalent_it_should_succeed() + { + // Arrange + object subject = EnumOne.One; + object expectation = EnumOne.One; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_the_actual_enum_value_is_null_it_should_report_that_properly() + { + // Arrange + var subject = new { NullableEnum = (DayOfWeek?)null }; + + var expectation = new { NullableEnum = DayOfWeek.Friday }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected*5*null*"); + } + + [Fact] + public void When_the_actual_enum_name_is_null_it_should_report_that_properly() + { + // Arrange + var subject = new { NullableEnum = (DayOfWeek?)null }; + + var expectation = new { NullableEnum = DayOfWeek.Friday }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, o => o.ComparingEnumsByValue()); + + // Assert + act.Should().Throw() + .WithMessage("Expected*5*null*"); + } + + [Fact] + public void When_asserting_different_enum_members_are_equivalent_it_should_fail() + { + // Arrange + object subject = EnumOne.One; + object expectation = EnumOne.Two; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected *EnumOne.Two {value: 3}*but*EnumOne.One {value: 0}*"); + } + + [Fact] + public void When_asserting_members_from_different_enum_types_are_equivalent_it_should_compare_by_value_by_default() + { + // Arrange + var subject = new ClassWithEnumOne(); + var expectation = new ClassWithEnumTwo(); + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_members_from_different_enum_types_are_equivalent_by_value_it_should_succeed() + { + // Arrange + var subject = new ClassWithEnumOne { Enum = EnumOne.One }; + var expectation = new ClassWithEnumThree { Enum = EnumThree.ValueZero }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, config => config.ComparingEnumsByValue()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_members_from_different_enum_types_are_equivalent_by_string_value_it_should_succeed() + { + // Arrange + var subject = new ClassWithEnumOne { Enum = EnumOne.Two }; + var expectation = new ClassWithEnumThree() { Enum = EnumThree.Two }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, config => config.ComparingEnumsByName()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void + When_asserting_members_from_different_enum_types_are_equivalent_by_value_but_comparing_by_name_it_should_throw() + { + // Arrange + var subject = new ClassWithEnumOne { Enum = EnumOne.Two }; + var expectation = new ClassWithEnumFour { Enum = EnumFour.Three }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, config => config.ComparingEnumsByName()); + + // Assert + act.Should().Throw() + .WithMessage("Expected*to equal EnumFour.Three {value: 3} by name, but found EnumOne.Two {value: 3}*"); + } + + [Fact] + public void When_asserting_members_from_different_char_enum_types_are_equivalent_by_value_it_should_succeed() + { + // Arrange + var subject = new ClassWithEnumCharOne { Enum = EnumCharOne.B }; + var expectation = new ClassWithEnumCharTwo { Enum = EnumCharTwo.ValueB }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, config => config.ComparingEnumsByValue()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_enums_typed_as_object_are_equivalent_it_should_succeed() + { + // Arrange + object e1 = EnumOne.One; + object e2 = EnumOne.One; + + // Act + Action act = () => e1.Should().BeEquivalentTo(e2); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_numeric_member_is_compared_with_an_enum_it_should_throw() + { + // Arrange + var actual = new { Property = 1 }; + + var expected = new { Property = TestEnum.First }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected, options => options.ComparingEnumsByValue()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_string_member_is_compared_with_an_enum_it_should_throw() + { + // Arrange + var actual = new { Property = "First" }; + + var expected = new { Property = TestEnum.First }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected, options => options.ComparingEnumsByName()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_null_enum_members_are_compared_by_name_it_should_succeed() + { + // Arrange + var actual = new { Property = null as TestEnum? }; + + var expected = new { Property = null as TestEnum? }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected, options => options.ComparingEnumsByName()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_null_enum_members_are_compared_by_value_it_should_succeed() + { + // Arrange + var actual = new { Property = null as TestEnum? }; + + var expected = new { Property = null as TestEnum? }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected, options => options.ComparingEnumsByValue()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_zero_and_null_enum_are_compared_by_value_it_should_throw() + { + // Arrange + var actual = new { Property = (TestEnum)0 }; + + var expected = new { Property = null as TestEnum? }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected, options => options.ComparingEnumsByValue()); + + // Assert + act.Should().Throw(); + } + + public enum TestEnum + { + First = 1 + } + + [Fact] + public void When_subject_is_null_and_enum_has_some_value_it_should_throw() + { + // Arrange + object subject = null; + object expectedEnum = EnumULong.UInt64Max; + + // Act + Action act = () => + subject.Should().BeEquivalentTo(expectedEnum, x => x.ComparingEnumsByName(), "comparing enums should throw"); + + // Assert + act.Should().Throw() + .WithMessage( + "Expected*to be equivalent to EnumULong.UInt64Max {value: 18446744073709551615} because comparing enums should throw, but found *"); + } + + [Fact] + public void When_expectation_is_null_and_subject_enum_has_some_value_it_should_throw_with_a_useful_message() + { + // Arrange + object subjectEnum = EnumULong.UInt64Max; + object expected = null; + + // Act + Action act = () => + subjectEnum.Should().BeEquivalentTo(expected, x => x.ComparingEnumsByName(), "comparing enums should throw"); + + // Assert + act.Should().Throw() + .WithMessage("Expected*to be because comparing enums should throw, but found EnumULong.UInt64Max*"); + } + + [Fact] + public void When_both_enums_are_equal_and_greater_than_max_long_it_should_not_throw() + { + // Arrange + object enumOne = EnumULong.UInt64Max; + object enumTwo = EnumULong.UInt64Max; + + // Act + Action act = () => enumOne.Should().BeEquivalentTo(enumTwo); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_both_enums_are_equal_and_of_different_underlying_types_it_should_not_throw() + { + // Arrange + object enumOne = EnumLong.Int64Max; + object enumTwo = EnumULong.Int64Max; + + // Act + Action act = () => enumOne.Should().BeEquivalentTo(enumTwo); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_both_enums_are_large_and_not_equal_it_should_throw() + { + // Arrange + object subjectEnum = EnumLong.Int64LessOne; + object expectedEnum = EnumULong.UInt64Max; + + // Act + Action act = () => subjectEnum.Should().BeEquivalentTo(expectedEnum, "comparing enums should throw"); + + // Assert + act.Should().Throw() + .WithMessage( + "Expected subjectEnum*to equal EnumULong.UInt64Max {value: 18446744073709551615} by value because comparing enums should throw, but found EnumLong.Int64LessOne {value: 9223372036854775806}*"); + } + } +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/ExtensibilityRelatedEquivalencySpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/ExtensibilitySpecs.cs similarity index 99% rename from Tests/FluentAssertions.Equivalency.Specs/ExtensibilityRelatedEquivalencySpecs.cs rename to Tests/FluentAssertions.Equivalency.Specs/ExtensibilitySpecs.cs index 8b13078bd5..1186557657 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/ExtensibilityRelatedEquivalencySpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/ExtensibilitySpecs.cs @@ -3,17 +3,16 @@ using System.Globalization; using System.Linq; using System.Reflection; -using FluentAssertions.Equivalency; using FluentAssertions.Extensions; using Xunit; using Xunit.Sdk; -namespace FluentAssertions.Specs.Equivalency +namespace FluentAssertions.Equivalency.Specs { /// /// Test Class containing specs over the extensibility points of Should().BeEquivalentTo /// - public class ExtensibilityRelatedEquivalencySpecs + public class ExtensibilitySpecs { #region Selection Rules diff --git a/Tests/FluentAssertions.Equivalency.Specs/FluentAssertions.Equivalency.Specs.csproj b/Tests/FluentAssertions.Equivalency.Specs/FluentAssertions.Equivalency.Specs.csproj index 7afa12dfc5..d57c2739bb 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/FluentAssertions.Equivalency.Specs.csproj +++ b/Tests/FluentAssertions.Equivalency.Specs/FluentAssertions.Equivalency.Specs.csproj @@ -8,7 +8,6 @@ ..\..\Src\FluentAssertions\FluentAssertions.snk false ..\..\TestRules.ruleset - $(NoWarn),IDE0052,1573,1591,1712 false true diff --git a/Tests/FluentAssertions.Equivalency.Specs/IsExternalInit.cs b/Tests/FluentAssertions.Equivalency.Specs/IsExternalInit.cs new file mode 100644 index 0000000000..407b1e030c --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/IsExternalInit.cs @@ -0,0 +1,14 @@ +using System.ComponentModel; + +// ReSharper disable once CheckNamespace +namespace System.Runtime.CompilerServices +{ + /// + /// Reserved to be used by the compiler for tracking metadata. + /// This class should not be used by developers in source code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + internal static class IsExternalInit + { + } +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/MemberConversionSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/MemberConversionSpecs.cs new file mode 100644 index 0000000000..1658864dfa --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/MemberConversionSpecs.cs @@ -0,0 +1,156 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Equivalency.Specs +{ + public class MemberConversionSpecs + { + [Fact] + public void When_two_objects_have_the_same_properties_with_convertable_values_it_should_succeed() + { + // Arrange + var subject = new { Age = "37", Birthdate = "1973-09-20" }; + + var other = new { Age = 37, Birthdate = new DateTime(1973, 9, 20) }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(other, o => o.WithAutoConversion()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_string_is_declared_equivalent_to_an_int_representing_the_numerals_it_should_pass() + { + // Arrange + var actual = new { Property = "32" }; + + var expectation = new { Property = 32 }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expectation, + options => options.WithAutoConversion()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_an_int_is_compared_equivalent_to_a_string_representing_the_number_it_should_pass() + { + // Arrange + var subject = new { Property = 32 }; + var expectation = new { Property = "32" }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, options => options.WithAutoConversion()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_injecting_a_null_predicate_into_WithAutoConversionFor_it_should_throw() + { + // Arrange + var subject = new object(); + + var expectation = new object(); + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, + options => options.WithAutoConversionFor(predicate: null)); + + // Assert + act.Should().ThrowExactly() + .WithParameterName("predicate"); + } + + [Fact] + public void When_only_a_single_property_is_and_can_be_converted_but_the_other_one_doesnt_match_it_should_throw() + { + // Arrange + var subject = new { Age = 32, Birthdate = "1973-09-20" }; + + var expectation = new { Age = "32", Birthdate = new DateTime(1973, 9, 20) }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, + options => options.WithAutoConversionFor(x => x.Path.Contains("Birthdate"))); + + // Assert + act.Should().Throw().WithMessage("*Age*String*Int32*"); + } + + [Fact] + public void When_only_a_single_property_is_converted_and_the_other_matches_it_should_succeed() + { + // Arrange + var subject = new { Age = 32, Birthdate = "1973-09-20" }; + + var expectation = new { Age = 32, Birthdate = new DateTime(1973, 9, 20) }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, options => options + .WithAutoConversionFor(x => x.Path.Contains("Birthdate"))); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_injecting_a_null_predicate_into_WithoutAutoConversionFor_it_should_throw() + { + // Arrange + var subject = new object(); + + var expectation = new object(); + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, + options => options.WithoutAutoConversionFor(predicate: null)); + + // Assert + act.Should().ThrowExactly() + .WithParameterName("predicate"); + } + + [Fact] + public void When_a_specific_mismatching_property_is_excluded_from_conversion_it_should_throw() + { + // Arrange + var subject = new { Age = 32, Birthdate = "1973-09-20" }; + + var expectation = new { Age = 32, Birthdate = new DateTime(1973, 9, 20) }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, options => options + .WithAutoConversion() + .WithoutAutoConversionFor(x => x.Path.Contains("Birthdate"))); + + // Assert + act.Should().Throw().Which.Message + .Should().Match("Expected*<1973-09-20>*\"1973-09-20\"*", "{0} field is of mismatched type", + nameof(expectation.Birthdate)) + .And.Subject.Should().Match("*Try conversion of all members*", "conversion description should be present") + .And.Subject.Should().NotMatch("*Try conversion of all members*Try conversion of all members*", + "conversion description should not be duplicated"); + } + + [Fact] + public void When_declaring_equivalent_a_convertable_object_that_is_equivalent_once_converted_it_should_pass() + { + // Arrange + string str = "This is a test"; + CustomConvertible obj = new CustomConvertible(str); + + // Act + Action act = () => obj.Should().BeEquivalentTo(str, options => options.WithAutoConversion()); + + // Assert + act.Should().NotThrow(); + } + } +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/MemberLessObjectsSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/MemberLessObjectsSpecs.cs new file mode 100644 index 0000000000..4d7de019e6 --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/MemberLessObjectsSpecs.cs @@ -0,0 +1,161 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Equivalency.Specs +{ + public class MemberLessObjectsSpecs + { + [Fact] + public void When_asserting_instances_of_an_anonymous_type_having_no_members_are_equivalent_it_should_fail() + { + // Arrange / Act + Action act = () => new { }.Should().BeEquivalentTo(new { }); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_asserting_instances_of_a_class_having_no_members_are_equivalent_it_should_fail() + { + // Arrange / Act + Action act = () => new ClassWithNoMembers().Should().BeEquivalentTo(new ClassWithNoMembers()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_asserting_instances_of_Object_are_equivalent_it_should_fail() + { + // Arrange / Act + Action act = () => new object().Should().BeEquivalentTo(new object()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_asserting_instance_of_object_is_equivalent_to_null_it_should_fail_with_a_descriptive_message() + { + // Arrange + object actual = new object(); + object expected = null; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected, "we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage("*Expected*to be *we want to test the failure message*, but found System.Object*"); + } + + [Fact] + public void When_asserting_null_is_equivalent_to_instance_of_object_it_should_fail() + { + // Arrange + object actual = null; + object expected = new object(); + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected); + + // Assert + act.Should().Throw() + .WithMessage("*Expected*to be System.Object*but found *"); + } + + [Fact] + public void When_an_type_only_exposes_fields_but_fields_are_ignored_in_the_equivalence_comparision_it_should_fail() + { + // Arrange + var object1 = new ClassWithOnlyAField { Value = 1 }; + var object2 = new ClassWithOnlyAField { Value = 101 }; + + // Act + Action act = () => object1.Should().BeEquivalentTo(object2, opts => opts.IncludingAllDeclaredProperties()); + + // Assert + act.Should().Throw("the objects have no members to compare."); + } + + [Fact] + public void + When_an_type_only_exposes_properties_but_properties_are_ignored_in_the_equivalence_comparision_it_should_fail() + { + // Arrange + var object1 = new ClassWithOnlyAProperty { Value = 1 }; + var object2 = new ClassWithOnlyAProperty { Value = 101 }; + + // Act + Action act = () => object1.Should().BeEquivalentTo(object2, opts => opts.ExcludingProperties()); + + // Assert + act.Should().Throw("the objects have no members to compare."); + } + + [Fact] + public void When_asserting_instances_of_arrays_of_types_in_System_are_equivalent_it_should_respect_the_runtime_type() + { + // Arrange + object actual = new int[0]; + object expectation = new int[0]; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_throwing_on_missing_members_and_there_are_no_missing_members_should_not_throw() + { + // Arrange + var subject = new { Version = 2, Age = 36, }; + + var expectation = new { Version = 2, Age = 36 }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, + options => options.ThrowingOnMissingMembers()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_throwing_on_missing_members_and_there_is_a_missing_member_should_throw() + { + // Arrange + var subject = new { Version = 2 }; + + var expectation = new { Version = 2, Age = 36 }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, + options => options.ThrowingOnMissingMembers()); + + // Assert + act.Should().Throw() + .WithMessage("Expectation has property subject.Age that the other object does not have*"); + } + + [Fact] + public void When_throwing_on_missing_members_and_there_is_an_additional_property_on_subject_should_not_throw() + { + // Arrange + var subject = new { Version = 2, Age = 36, Additional = 13 }; + + var expectation = new { Version = 2, Age = 36 }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, + options => options.ThrowingOnMissingMembers()); + + // Assert + act.Should().NotThrow(); + } + } +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/MemberMatchingSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/MemberMatchingSpecs.cs new file mode 100644 index 0000000000..990000bce1 --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/MemberMatchingSpecs.cs @@ -0,0 +1,472 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Equivalency.Specs +{ + public class MemberMatchingSpecs + { + [Fact] + [SuppressMessage("ReSharper", "StringLiteralTypo")] + public void When_using_ExcludingMissingMembers_both_fields_and_properties_should_be_ignored() + { + // Arrange + var class1 = new ClassWithSomeFieldsAndProperties + { + Field1 = "Lorem", + Field2 = "ipsum", + Field3 = "dolor", + Property1 = "sit", + Property2 = "amet", + Property3 = "consectetur" + }; + + var class2 = new { Field1 = "Lorem" }; + + // Act + Action act = + () => class1.Should().BeEquivalentTo(class2, opts => opts.ExcludingMissingMembers()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_property_shared_by_anonymous_types_doesnt_match_it_should_throw() + { + // Arrange + var subject = new { Age = 36 }; + + var other = new { Age = 37 }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(other, options => options.ExcludingMissingMembers()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Nested_properties_can_be_mapped_using_a_nested_expression() + { + // Arrange + var subject = new ParentOfSubjectWithProperty1(new[] { new SubjectWithProperty1 { Property1 = "Hello" } }); + + var expectation = new ParentOfExpectationWithProperty2(new[] + { + new ExpectationWithProperty2 { Property2 = "Hello" } + }); + + // Act / Assert + subject.Should() + .BeEquivalentTo(expectation, opt => opt + .WithMapping( + e => e.Parent[0].Property2, + s => s.Parent[0].Property1)); + } + + [Fact] + 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 expectation = new ParentOfExpectationWithProperty2(new[] + { + new ExpectationWithProperty2 { Property2 = "Hello" } + }); + + // Act / Assert + subject.Should() + .BeEquivalentTo(expectation, opt => opt + .WithMapping("Property2", "Property1")); + } + + [Fact] + public void Nested_fields_can_be_mapped_using_a_nested_type_and_field_names() + { + // Arrange + var subject = new ClassWithSomeFieldsAndProperties { Field1 = "John", Field2 = "Mary" }; + + var expectation = new ClassWithSomeFieldsAndProperties { Field1 = "Mary", Field2 = "John" }; + + // Act / Assert + subject.Should() + .BeEquivalentTo(expectation, opt => opt + .WithMapping("Field1", "Field2") + .WithMapping("Field2", "Field1")); + } + + [Fact] + 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 expectation = new ParentOfExpectationWithProperty2(new[] + { + new ExpectationWithProperty2 { Property2 = "Hello" } + }); + + // Act / Assert + subject.Should() + .BeEquivalentTo(expectation, opt => opt + .WithMapping( + e => e.Property2, s => s.Property1)); + } + + [Fact] + public void Nested_properties_on_a_collection_can_be_mapped_using_a_dotted_path() + { + // Arrange + var subject = new { Parent = new[] { new SubjectWithProperty1 { Property1 = "Hello" } } }; + + var expectation = new { Parent = new[] { new ExpectationWithProperty2 { Property2 = "Hello" } } }; + + // Act / Assert + subject.Should() + .BeEquivalentTo(expectation, opt => opt + .WithMapping("Parent[].Property2", "Parent[].Property1")); + } + + [Fact] + public void Properties_can_be_mapped_by_name() + { + // Arrange + var subject = new SubjectWithProperty1 { Property1 = "Hello" }; + + var expectation = new ExpectationWithProperty2 { Property2 = "Hello" }; + + // Act / Assert + subject.Should() + .BeEquivalentTo(expectation, opt => opt + .WithMapping("Property2", "Property1")); + } + + [Fact] + public void Fields_can_be_mapped_by_name() + { + // Arrange + var subject = new ClassWithSomeFieldsAndProperties { Field1 = "Hello", Field2 = "John" }; + + var expectation = new ClassWithSomeFieldsAndProperties { Field1 = "John", Field2 = "Hello" }; + + // Act / Assert + subject.Should() + .BeEquivalentTo(expectation, opt => opt + .WithMapping("Field2", "Field1") + .WithMapping("Field1", "Field2")); + } + + [Fact] + public void Fields_can_be_mapped_to_a_property_by_name() + { + // Arrange + var subject = new ClassWithSomeFieldsAndProperties { Property1 = "John" }; + + var expectation = new ClassWithSomeFieldsAndProperties { Field1 = "John", }; + + // Act / Assert + subject.Should() + .BeEquivalentTo(expectation, opt => opt + .WithMapping("Field1", "Property1") + .Including(e => e.Field1)); + } + + [Fact] + public void Properties_can_be_mapped_to_a_field_by_expression() + { + // Arrange + var subject = new ClassWithSomeFieldsAndProperties { Field1 = "John", }; + + var expectation = new ClassWithSomeFieldsAndProperties { Property1 = "John" }; + + // Act / Assert + subject.Should() + .BeEquivalentTo(expectation, opt => opt + .WithMapping(e => e.Property1, s => s.Field1) + .Including(e => e.Property1)); + } + + [Fact] + public void Properties_can_be_mapped_to_inherited_properties() + { + // Arrange + var subject = new Derived { BaseProperty = "Hello World" }; + + var expectation = new { AnotherProperty = "Hello World" }; + + // Act / Assert + subject.Should() + .BeEquivalentTo(expectation, opt => opt + .WithMapping(e => e.AnotherProperty, s => s.BaseProperty)); + } + + [Fact] + public void A_failed_assertion_reports_the_subjects_mapped_property() + { + // Arrange + var subject = new SubjectWithProperty1 { Property1 = "Hello" }; + + var expectation = new ExpectationWithProperty2 { Property2 = "Hello2" }; + + // Act + Action act = () => subject.Should() + .BeEquivalentTo(expectation, opt => opt + .WithMapping(e => e.Property2, e => e.Property1)); + + // Assert + act.Should() + .Throw() + .WithMessage("Expected property subject.Property1 to be*Hello*"); + } + + [Fact] + public void An_empty_expectation_member_path_is_not_allowed() + { + var subject = new SubjectWithProperty1(); + var expectation = new ExpectationWithProperty2(); + + // Act + Action act = () => subject.Should() + .BeEquivalentTo(expectation, opt => opt + .WithMapping("", "Parent[0].Property1")); + + // Assert + act.Should() + .Throw() + .WithMessage("*member path*"); + } + + [Fact] + public void An_empty_subject_member_path_is_not_allowed() + { + var subject = new SubjectWithProperty1(); + var expectation = new ExpectationWithProperty2(); + + // Act + Action act = () => subject.Should() + .BeEquivalentTo(expectation, opt => opt + .WithMapping("Parent[0].Property1", "")); + + // Assert + act.Should() + .Throw() + .WithMessage("*member path*"); + } + + [Fact] + public void Null_as_the_expectation_member_path_is_not_allowed() + { + var subject = new SubjectWithProperty1(); + var expectation = new ExpectationWithProperty2(); + + // Act + Action act = () => subject.Should() + .BeEquivalentTo(expectation, opt => opt + .WithMapping(null, "Parent[0].Property1")); + + // Assert + act.Should() + .Throw() + .WithMessage("*member path*"); + } + + [Fact] + public void Null_as_the_subject_member_path_is_not_allowed() + { + var subject = new SubjectWithProperty1(); + var expectation = new ExpectationWithProperty2(); + + // Act + Action act = () => subject.Should() + .BeEquivalentTo(expectation, opt => opt + .WithMapping("Parent[0].Property1", null)); + + // Assert + act.Should() + .Throw() + .WithMessage("*member path*"); + } + + [Fact] + public void Subject_and_expectation_member_paths_must_have_the_same_parent() + { + var subject = new SubjectWithProperty1(); + var expectation = new ExpectationWithProperty2(); + + // Act + Action act = () => subject.Should() + .BeEquivalentTo(expectation, opt => opt + .WithMapping("Parent[].Property1", "OtherParent[].Property2")); + + // Assert + act.Should() + .Throw() + .WithMessage("*parent*"); + } + + [Fact] + public void Numeric_indexes_in_the_path_are_not_allowed() + { + var subject = new { Parent = new[] { new SubjectWithProperty1 { Property1 = "Hello" } } }; + + var expectation = new { Parent = new[] { new ExpectationWithProperty2 { Property2 = "Hello" } } }; + + // Act + Action act = () => subject.Should() + .BeEquivalentTo(expectation, opt => opt + .WithMapping("Parent[0].Property2", "Parent[0].Property1")); + + // Assert + act.Should() + .Throw() + .WithMessage("*without specific index*"); + } + + [Fact] + public void Mapping_to_a_non_existing_subject_member_is_not_allowed() + { + // Arrange + var subject = new SubjectWithProperty1 { Property1 = "Hello" }; + + var expectation = new ExpectationWithProperty2 { Property2 = "Hello" }; + + // Act + Action act = () => subject.Should() + .BeEquivalentTo(expectation, opt => opt + .WithMapping("Property2", "NonExistingProperty")); + + // Assert + act.Should() + .Throw() + .WithMessage("*not have member NonExistingProperty*"); + } + + [Fact] + public void A_null_subject_should_result_in_a_normal_assertion_failure() + { + // Arrange + SubjectWithProperty1 subject = null; + + ExpectationWithProperty2 expectation = new() { Property2 = "Hello" }; + + // Act + Action act = () => subject.Should() + .BeEquivalentTo(expectation, opt => opt + .WithMapping("Property2", "Property1")); + + // Assert + act.Should() + .Throw() + .WithMessage("*Expected*ExpectationWithProperty2*found *"); + } + + [Fact] + public void Nested_types_and_dotted_expectation_member_paths_cannot_be_combined() + { + // Arrange + var subject = new ParentOfSubjectWithProperty1(new[] { new SubjectWithProperty1 { Property1 = "Hello" } }); + + var expectation = new ParentOfExpectationWithProperty2(new[] + { + new ExpectationWithProperty2 { Property2 = "Hello" } + }); + + // Act + Action act = () => subject.Should() + .BeEquivalentTo(expectation, opt => opt + .WithMapping("Parent.Property2", "Property1")); + + // Assert + act.Should() + .Throw() + .WithMessage("*cannot be a nested path*"); + } + + [Fact] + public void Nested_types_and_dotted_subject_member_paths_cannot_be_combined() + { + // Arrange + var subject = new ParentOfSubjectWithProperty1(new[] { new SubjectWithProperty1 { Property1 = "Hello" } }); + + var expectation = new ParentOfExpectationWithProperty2(new[] + { + new ExpectationWithProperty2 { Property2 = "Hello" } + }); + + // Act + Action act = () => subject.Should() + .BeEquivalentTo(expectation, opt => opt + .WithMapping("Property2", "Parent.Property1")); + + // Assert + act.Should() + .Throw() + .WithMessage("*cannot be a nested path*"); + } + + [Fact] + 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 expectation = new ParentOfExpectationWithProperty2(new[] + { + new ExpectationWithProperty2 { Property2 = "Hello" } + }); + + // Act + Action act = () => subject.Should() + .BeEquivalentTo(expectation, opt => opt + .WithMapping("Property2", "NonExistingProperty")); + + // Assert + act.Should() + .Throw() + .WithMessage("*does not have member NonExistingProperty*"); + } + + internal class ParentOfExpectationWithProperty2 + { + public ExpectationWithProperty2[] Parent { get; } + + public ParentOfExpectationWithProperty2(ExpectationWithProperty2[] parent) + { + Parent = parent; + } + } + + internal class ParentOfSubjectWithProperty1 + { + public SubjectWithProperty1[] Parent { get; } + + public ParentOfSubjectWithProperty1(SubjectWithProperty1[] parent) + { + Parent = parent; + } + } + + internal class SubjectWithProperty1 + { + public string Property1 { get; set; } + } + + internal class ExpectationWithProperty2 + { + public string Property2 { get; set; } + } + + internal class Base + { + public string BaseProperty { get; set; } + } + + internal class Derived : Base + { + public string DerivedProperty { get; set; } + } + } +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/NestedPropertiesSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/NestedPropertiesSpecs.cs new file mode 100644 index 0000000000..0ac1c5b3b1 --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/NestedPropertiesSpecs.cs @@ -0,0 +1,322 @@ +using System; +using System.Collections.Generic; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Equivalency.Specs +{ + public class NestedPropertiesSpecs + { + [Fact] + public void When_all_the_properties_of_the_nested_objects_are_equal_it_should_succeed() + { + // Arrange + var subject = new Root + { + Text = "Root", Level = new Level1 { Text = "Level1", Level = new Level2 { Text = "Level2" } } + }; + + var expected = new RootDto + { + Text = "Root", Level = new Level1Dto { Text = "Level1", Level = new Level2Dto { Text = "Level2" } } + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_the_expectation_contains_a_nested_null_it_should_properly_report_the_difference() + { + // Arrange + var subject = new Root { Text = "Root", Level = new Level1 { Text = "Level1", Level = new Level2() } }; + + var expected = new RootDto { Text = "Root", Level = new Level1Dto { Text = "Level1", Level = null } }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected); + + // Assert + act.Should().Throw() + .WithMessage("*Expected*Level.Level*to be , but found*Level2*Without automatic conversion*"); + } + + [Fact] + public void + When_not_all_the_properties_of_the_nested_objects_are_equal_but_nested_objects_are_excluded_it_should_succeed() + { + // Arrange + var subject = new + { + Property = new ClassWithValueSemanticsOnSingleProperty + { + Key = "123", NestedProperty = "Should be ignored" + } + }; + + var expected = new + { + Property = new ClassWithValueSemanticsOnSingleProperty + { + Key = "123", NestedProperty = "Should be ignored as well" + } + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected, + options => options.ExcludingNestedObjects()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_nested_objects_should_be_excluded_it_should_do_a_simple_equality_check_instead() + { + // Arrange + var item = new Item { Child = new Item() }; + + // Act + Action act = () => item.Should().BeEquivalentTo(new Item(), options => options.ExcludingNestedObjects()); + + // Assert + act.Should().Throw() + .WithMessage("Expected*Item*null*"); + } + + public class Item + { + public Item Child { get; set; } + } + + [Fact] + public void When_not_all_the_properties_of_the_nested_objects_are_equal_it_should_throw() + { + // Arrange + var subject = new Root { Text = "Root", Level = new Level1 { Text = "Level1" } }; + + var expected = new RootDto { Text = "Root", Level = new Level1Dto { Text = "Level2" } }; + + // Act + Action act = () => + subject.Should().BeEquivalentTo(expected); + + // Assert + act.Should().Throw().Which.Message + + // Checking exception message exactly is against general guidelines + // but in that case it was done on purpose, so that we have at least have a single + // test confirming that whole mechanism of gathering description from + // equivalency steps works. + .Should().Match( + @"Expected property subject.Level.Text to be ""Level2"", but ""Level1"" differs near ""1"" (index 5).*" + + "With configuration:*" + + "- Use declared types and 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)*" + + "- Be strict about the order of items in byte arrays*" + + "- Without automatic conversion.*"); + } + + [Fact] + public void When_the_actual_nested_object_is_null_it_should_throw() + { + // Arrange + var subject = new Root { Text = "Root", Level = new Level1 { Text = "Level2" } }; + + var expected = new RootDto { Text = "Root", Level = null }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected); + + // Assert + act + .Should().Throw() + .WithMessage("Expected*Level*to be *, but found*Level1*Level2*"); + } + + public class StringSubContainer + { + public string SubValue { get; set; } + } + + public class StringContainer + { + public StringContainer(string mainValue, string subValue = null) + { + MainValue = mainValue; + SubValues = new[] { new StringSubContainer { SubValue = subValue } }; + } + + public string MainValue { get; set; } + + public IList SubValues { get; set; } + } + + public class MyClass2 + { + public StringContainer One { get; set; } + + public StringContainer Two { get; set; } + } + + [Fact] + public void When_deeply_nested_strings_dont_match_it_should_properly_report_the_mismatches() + { + // Arrange + var expected = new[] + { + new MyClass2 { One = new StringContainer("EXPECTED", "EXPECTED"), Two = new StringContainer("CORRECT") }, + new MyClass2() + }; + + var actual = new[] + { + new MyClass2 + { + One = new StringContainer("INCORRECT", "INCORRECT"), Two = new StringContainer("CORRECT") + }, + new MyClass2() + }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected); + + // Assert + act.Should().Throw() + .WithMessage("*EXPECTED*INCORRECT*EXPECTED*INCORRECT*"); + } + + [Fact] + public void When_the_nested_object_property_is_null_it_should_throw() + { + // Arrange + var subject = new Root { Text = "Root", Level = null }; + + var expected = new RootDto { Text = "Root", Level = new Level1Dto { Text = "Level2" } }; + + // Act + Action act = () => + subject.Should().BeEquivalentTo(expected); + + // Assert + act + .Should().Throw() + .WithMessage("Expected property subject.Level*to be*Level1Dto*Level2*, but found *"); + } + + [Fact] + public void When_not_all_the_properties_of_the_nested_object_exist_on_the_expected_object_it_should_throw() + { + // Arrange + var subject = new { Level = new { Text = "Level1", } }; + + var expected = new { Level = new { Text = "Level1", OtherProperty = "OtherProperty" } }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected); + + // Assert + act + .Should().Throw() + .WithMessage("Expectation has property subject.Level.OtherProperty that the other object does not have*"); + } + + [Fact] + public void When_all_the_shared_properties_of_the_nested_objects_are_equal_it_should_succeed() + { + // Arrange + var subject = new { Level = new { Text = "Level1", Property = "Property" } }; + + var expected = new { Level = new { Text = "Level1", OtherProperty = "OtherProperty" } }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected, options => options.ExcludingMissingMembers()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_deeply_nested_properties_do_not_have_all_equal_values_it_should_throw() + { + // Arrange + var root = new Root + { + Text = "Root", Level = new Level1 { Text = "Level1", Level = new Level2 { Text = "Level2" } } + }; + + var rootDto = new RootDto + { + Text = "Root", + Level = new Level1Dto { Text = "Level1", Level = new Level2Dto { Text = "A wrong text value" } } + }; + + // Act + Action act = () => root.Should().BeEquivalentTo(rootDto); + + // Assert + act + .Should().Throw() + .WithMessage( + "Expected*Level.Level.Text*to be *A wrong text value*but*\"Level2\"*length*"); + } + + [Fact] + public void When_two_objects_have_the_same_nested_objects_it_should_not_throw() + { + // Arrange + var c1 = new ClassOne(); + var c2 = new ClassOne(); + + // Act + Action act = () => c1.Should().BeEquivalentTo(c2); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_property_of_a_nested_object_doesnt_match_it_should_clearly_indicate_the_path() + { + // Arrange + var c1 = new ClassOne(); + var c2 = new ClassOne(); + c2.RefOne.ValTwo = 2; + + // Act + Action act = () => c1.Should().BeEquivalentTo(c2); + + // Assert + act.Should().Throw() + .WithMessage("Expected property c1.RefOne.ValTwo to be 2, but found 3*"); + } + + [Fact] + public void Should_support_nested_collections_containing_empty_objects() + { + // Arrange + var orig = new[] { new OuterWithObject { MyProperty = new[] { new Inner() } } }; + + var expectation = new[] { new OuterWithObject { MyProperty = new[] { new Inner() } } }; + + // Act / Assert + orig.Should().BeEquivalentTo(expectation); + } + + public class Inner + { + } + + public class OuterWithObject + { + public Inner[] MyProperty { get; set; } + } + } +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/BasicNonEquivalencySpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/NonEquivalencySpecs.cs similarity index 96% rename from Tests/FluentAssertions.Equivalency.Specs/BasicNonEquivalencySpecs.cs rename to Tests/FluentAssertions.Equivalency.Specs/NonEquivalencySpecs.cs index 4a08858c23..ce00038800 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/BasicNonEquivalencySpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/NonEquivalencySpecs.cs @@ -2,9 +2,9 @@ using Xunit; using Xunit.Sdk; -namespace FluentAssertions.Specs.Equivalency +namespace FluentAssertions.Equivalency.Specs { - public class BasicNonEquivalencySpecs + public class NonEquivalencySpecs { [Fact] public void When_asserting_inequivalence_of_equal_ints_as_object_it_should_fail() diff --git a/Tests/FluentAssertions.Equivalency.Specs/ObjectReferenceSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/ObjectReferenceSpecs.cs index 8acbd005f2..53bff00c07 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/ObjectReferenceSpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/ObjectReferenceSpecs.cs @@ -1,8 +1,7 @@ -using FluentAssertions.Equivalency; -using FluentAssertions.Equivalency.Execution; +using FluentAssertions.Equivalency.Execution; using Xunit; -namespace FluentAssertions.Specs.Equivalency +namespace FluentAssertions.Equivalency.Specs { public class ObjectReferenceSpecs { diff --git a/Tests/FluentAssertions.Equivalency.Specs/RecordSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/RecordSpecs.cs new file mode 100644 index 0000000000..005f6dee22 --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/RecordSpecs.cs @@ -0,0 +1,94 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Equivalency.Specs +{ + 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 expected = new MyRecord { StringField = "foo", CollectionProperty = new[] { "foo", "bar", "zip" } }; + + actual.Should().BeEquivalentTo(expected); + } + + [Fact] + public void When_the_subject_is_a_record_it_should_mention_that_in_the_configuration_output() + { + var actual = new MyRecord { StringField = "foo", }; + + var expected = new MyRecord { StringField = "bar", }; + + Action act = () => actual.Should().BeEquivalentTo(expected); + + act.Should().Throw() + .WithMessage("*Compare records by their members*"); + } + + [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 expected = new MyRecord { StringField = "foo", CollectionProperty = new[] { "foo", "bar", "zip" } }; + + Action act = () => actual.Should().BeEquivalentTo(expected, o => o + .ComparingByValue()); + + act.Should().Throw() + .WithMessage("*Expected*MyRecord*but found*MyRecord*") + .WithMessage("*Compare*MyRecord by value*"); + } + + [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 expected = new MyRecord { StringField = "foo", CollectionProperty = new[] { "foo", "bar", "zip" } }; + + Action act = () => actual.Should().BeEquivalentTo(expected, o => o + .ComparingRecordsByValue()); + + act.Should().Throw() + .WithMessage("*Expected*MyRecord*but found*MyRecord*") + .WithMessage("*Compare records by value*"); + } + + [Fact] + 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 expected = new MyRecord { StringField = "foo", CollectionProperty = new[] { "foo", "bar", "zip" } }; + + actual.Should().BeEquivalentTo(expected, o => o + .ComparingRecordsByValue() + .ComparingByMembers()); + } + + [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 expected = new MyRecord { CollectionProperty = new[] { "foo", "bar", "zip" } }; + + actual.Should().BeEquivalentTo(expected, o => o + .ComparingRecordsByValue() + .ComparingRecordsByMembers()); + } + + private record MyRecord + { + public string StringField; + + public string[] CollectionProperty { get; init; } + } + } +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.cs new file mode 100644 index 0000000000..84f742b874 --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.cs @@ -0,0 +1,1493 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using FluentAssertions.Common; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Equivalency.Specs +{ + public class SelectionRulesSpecs + { + private enum LocalOtherType : byte + { + Default, + NonDefault + } + + private enum LocalType : byte + { + Default, + NonDefault + } + + [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 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(); + } + + public class CustomType + { + public string Name { get; set; } + } + + public class ClassA + { + public List ListOfCustomTypes { 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 CustomType { Name = "A" }, + new CustomType { Name = "B" } + }; + + var list2 = new List + { + new CustomType { Name = "C" }, + new CustomType { 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*"); + } + + [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(_ => _.Field1).Including(_ => _.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(_ => _.Field1).Including(_ => _.Field2).Including(_ => _.Field3)); + + // Assert + act.Should().Throw().WithMessage("Expected field class1.Field3*"); + } + + [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] + [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(_ => _.Field3).Excluding(_ => _.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(_ => _.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_property_is_write_only_it_should_be_ignored() + { + // Arrange + var subject = new ClassWithWriteOnlyProperty + { + WriteOnlyProperty = 123, + SomeOtherProperty = "whatever" + }; + + var expected = 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_hidden_in_a_derived_class_it_should_ignore_it() + { + // Arrange + var subject = new SubclassA { Foo = "test" }; + var expectation = new SubclassB { Foo = "test" }; + + // Act + Action action = () => subject.Should().BeEquivalentTo(expectation); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void When_including_a_property_that_is_hidden_in_a_derived_class_it_should_select_the_correct_one() + { + // Arrange + var b1 = new ClassThatHidesBaseClassProperty(); + var b2 = new ClassThatHidesBaseClassProperty(); + + // Act / Assert + b1.Should().BeEquivalentTo(b2, config => config.Including(b => b.Property)); + } + + [Fact] + public void Excluding_a_property_hiding_a_base_class_property_should_not_reveal_the_latter() + { + // Arrange + var b1 = new ClassThatHidesBaseClassProperty(); + var b2 = new ClassThatHidesBaseClassProperty(); + + // Act + Action act = () => b1.Should().BeEquivalentTo(b2, config => config.Excluding(b => b.Property)); + + // Assert + act.Should().Throw().WithMessage("*No members were found *"); + } + + private class ClassWithGuidProperty + { + public string Property { get; set; } = Guid.NewGuid().ToString(); + } + + private class ClassThatHidesBaseClassProperty : ClassWithGuidProperty + { + public new string[] Property { get; set; } + } + + [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 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 + { + public string PublicProperty { get; set; } + + internal string InternalProperty { get; set; } + + 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 + { + public string PublicField; + + internal string InternalField; + + protected internal string ProtectedInternalField; + } + + [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 BaseWithFoo + { + public object Foo { get; set; } + } + + public class SubclassA : BaseWithFoo + { + public new T Foo + { + get { return (T)base.Foo; } + + set { base.Foo = value; } + } + } + + public class D + { + public object Foo { get; set; } + } + + public class SubclassB : D + { + public new T Foo + { + get { return (T)base.Foo; } + + set { base.Foo = value; } + } + } + + public class ClassWithIndexer + { + public object Foo { get; set; } + + public string this[int n] + { + get + { + return + n.ToString( + CultureInfo.InvariantCulture); + } + } + } + + [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 When_respecting_declared_types_explicit_interface_member_on_interfaced_subject_should_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.RespectingDeclaredTypes()); + + // Assert + action.Should().NotThrow(); + } + + [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_subject_should_not_be_used() + { + // Arrange + var expected = new Vehicle + { + VehicleId = 1 + }; + + var subject = new ExplicitVehicle + { + VehicleId = 2 + }; + ((IVehicle)subject).VehicleId = 1; + + // Act + Action action = () => subject.Should().BeEquivalentTo(expected, opt => opt.RespectingDeclaredTypes()); + + // 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, opt => opt.RespectingDeclaredTypes()); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void When_respecting_runtime_types_explicit_interface_member_on_subject_should_not_be_used() + { + // Arrange + var expected = new Vehicle + { + VehicleId = 1 + }; + + var subject = new ExplicitVehicle + { + VehicleId = 2 + }; + ((IVehicle)subject).VehicleId = 1; + + // 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_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, opt => opt.RespectingRuntimeTypes()); + + // Assert + action.Should().Throw(); + } + + [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_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", + "ignore-private-protected"); + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected, config => + config.Excluding(ctx => ctx.WhichGetterHas(CSharpAccessModifier.Internal) || + ctx.WhichGetterHas(CSharpAccessModifier.ProtectedInternal) || + ctx.WhichGetterHas(CSharpAccessModifier.PrivateProtected))); + + // 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", + "ignore-private-protected"); + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected, config => + config.Excluding(ctx => ctx.WhichSetterHas(CSharpAccessModifier.Internal) || + ctx.WhichSetterHas(CSharpAccessModifier.ProtectedInternal) || + ctx.WhichSetterHas(CSharpAccessModifier.Private) || + ctx.WhichSetterHas(CSharpAccessModifier.PrivateProtected))); + + // Assert + act.Should().NotThrow(); + } + + [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 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 ClassWithOnlyAProperty { Value = 1 }, + ["Bar"] = new ClassWithOnlyAProperty { Value = 2 } + } + }; + + var expected = new + { + List = new[] { new { Foo = 1, Bar = 2 }, new { Foo = 2, Bar = 4 } }.ToList(), + Dictionary = new Dictionary + { + ["Foo"] = new ClassWithOnlyAProperty { Value = 1 }, + ["Bar"] = new ClassWithOnlyAProperty { 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_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 ClassWithOnlyAProperty { Value = 1 }, + ["Bar"] = new ClassWithOnlyAProperty { Value = 2 } + } + }; + + var expected = new + { + List = new[] { new { Foo = 5, Bar = 2 }, new { Foo = 2, Bar = 4 } }.ToList(), + Dictionary = new Dictionary + { + ["Foo"] = new ClassWithOnlyAProperty { Value = 6 }, + ["Bar"] = new ClassWithOnlyAProperty { 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] + [SuppressMessage("ReSharper", "StringLiteralTypo")] + public void When_using_IncludingAllDeclaredProperties_fields_should_be_ignored() + { + // 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 When_using_IncludingAllRuntimeProperties_the_runtime_type_should_be_used_and_fields_should_be_ignored() + { + // 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 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] + [SuppressMessage("ReSharper", "StringLiteralTypo")] + public void When_respecting_the_runtime_type_is_configured_the_runtime_type_should_be_used_and_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 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 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 + { + public string Name { get; set; } + + public ClassWithoutProperty ClassWithoutProperty { get; } = new ClassWithoutProperty(); + } + + internal class ClassWithoutProperty + { + } + +#if NET5_0_OR_GREATER + + [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 + { + public string BaseProperty { get; set; } + } + + private class DerivedWithProperty : BaseWithProperty + { + public string DerivedProperty { get; set; } + } + + private abstract class BaseWithAbstractProperty + { + public abstract BaseWithProperty Property { get; } + } + + private sealed class DerivedWithCovariantOverride : BaseWithAbstractProperty + { + public override DerivedWithProperty Property { get; } + + public string OtherProp { get; set; } + + public DerivedWithCovariantOverride(DerivedWithProperty prop) + { + Property = prop; + } + } + +#endif + + 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 + { + } + + [Fact] + public void Excluding_an_interface_property_through_inheritance_should_work() + { + // Arrange + var actual = new IInterfaceWithTwoProperties[] + { + new DerivedClassImplementingInterface { Value1 = 1, Value2 = 2 } + }; + + var expected = new IInterfaceWithTwoProperties[] + { + 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 + var actual = new IInterfaceWithTwoProperties[] + { + new DerivedClassImplementingInterface { Value1 = 1, Value2 = 2 } + }; + + var expected = new IInterfaceWithTwoProperties[] + { + new DerivedClassImplementingInterface { Value1 = 999, Value2 = 2 } + }; + + // Act / Assert + actual.Should().BeEquivalentTo(expected, options => options + .Including(a => a.Value2) + .RespectingRuntimeTypes()); + } + } +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/TupleSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/TupleSpecs.cs new file mode 100644 index 0000000000..4754b6b441 --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/TupleSpecs.cs @@ -0,0 +1,37 @@ +using System; +using Xunit; + +namespace FluentAssertions.Equivalency.Specs +{ + public class TupleSpecs + { + [Fact] + public void When_a_nested_member_is_a_tuple_it_should_compare_its_property_for_equivalence() + { + // Arrange + var actual = new { Tuple = (new[] { "string1" }, new[] { "string2" }) }; + + var expected = new { Tuple = (new[] { "string1" }, new[] { "string2" }) }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_tuple_is_compared_it_should_compare_its_components() + { + // Arrange + var actual = new Tuple("Hello", true, new int[] { 3, 2, 1 }); + var expected = new Tuple("Hello", true, new int[] { 1, 2, 3 }); + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected); + + // Assert + act.Should().NotThrow(); + } + } +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/TypedDataSetSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/TypedDataSetSpecs.cs new file mode 100644 index 0000000000..f8ed55f17f --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/TypedDataSetSpecs.cs @@ -0,0 +1,544 @@ +using System; +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.ExcludingTables("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).ExcludingTables("TypedDataTable1")); + } + + [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)); + } + + [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) => 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)); + } + + [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) => dataTable.ParentRelations) + .ExcludingRelated((DataTable 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/DataEquivalencySpecs.DataTable.Typed.cs b/Tests/FluentAssertions.Equivalency.Specs/TypedDataTableSpecs.cs similarity index 99% rename from Tests/FluentAssertions.Equivalency.Specs/DataEquivalencySpecs.DataTable.Typed.cs rename to Tests/FluentAssertions.Equivalency.Specs/TypedDataTableSpecs.cs index d4734e0814..892a7ce4b3 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/DataEquivalencySpecs.DataTable.Typed.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/TypedDataTableSpecs.cs @@ -3,20 +3,16 @@ using System.Data; using System.Globalization; using System.Linq; - using FluentAssertions.Data; - using Xunit; using Xunit.Sdk; -namespace FluentAssertions.Specs +namespace FluentAssertions.Equivalency.Specs { /// /// DataTableEquivalency specs for typed data tables. /// - public partial class DataEquivalencySpecs - { - public class TypedDataTableEquivalencySpecs : DataEquivalencySpecs + public class TypedDataTableSpecs : DataSpecs { [Fact] public void When_DataTables_are_identical_it_should_succeed() @@ -669,4 +665,3 @@ public void When_data_matches_in_different_order_and_RowMatchMode_is_PrimaryKey_ } } } -} diff --git a/Tests/FluentAssertions.Equivalency.Specs/XmlEquivalencySpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/XmlSpecs.cs similarity index 97% rename from Tests/FluentAssertions.Equivalency.Specs/XmlEquivalencySpecs.cs rename to Tests/FluentAssertions.Equivalency.Specs/XmlSpecs.cs index 91cdd07ac9..c34da7e96b 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/XmlEquivalencySpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/XmlSpecs.cs @@ -3,9 +3,9 @@ using Xunit; using Xunit.Sdk; -namespace FluentAssertions.Specs.Equivalency +namespace FluentAssertions.Equivalency.Specs { - public class XmlEquivalencySpecs + public class XmlSpecs { [Fact] public void When_asserting_a_xml_selfclosing_document_is_equivalent_to_a_different_xml_document_with_same_structure_it_should_succeed() diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainEquivalentOf.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainEquivalentOf.cs index 8b849dc240..2394e1c789 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainEquivalentOf.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainEquivalentOf.cs @@ -1,6 +1,5 @@ using System; using FluentAssertions.Execution; -using FluentAssertions.Specs.Equivalency; using Xunit; using Xunit.Sdk; diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.SatisfyRespectively.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.SatisfyRespectively.cs index cc70cd81d3..a741187fe8 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.SatisfyRespectively.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.SatisfyRespectively.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using FluentAssertions.Execution; -using FluentAssertions.Specs.Equivalency; using Xunit; using Xunit.Sdk; @@ -54,10 +53,7 @@ public void When_collection_which_is_asserting_against_inspectors_is_null_it_sho { using var _ = new AssertionScope(); collection.Should().SatisfyRespectively( - new Action[] - { - x => x.Should().Be(1) - }, "because we want to test the failure {0}", "message"); + new Action[] { x => x.Should().Be(1) }, "because we want to test the failure {0}", "message"); }; // Assert @@ -72,10 +68,8 @@ public void When_collection_which_is_asserting_against_inspectors_is_empty_it_sh var collection = Enumerable.Empty(); // Act - Action act = () => collection.Should().SatisfyRespectively(new Action[] - { - x => x.Should().Be(1) - }, "because we want to test the failure {0}", "message"); + Action act = () => collection.Should().SatisfyRespectively(new Action[] { x => x.Should().Be(1) }, + "because we want to test the failure {0}", "message"); // Assert act.Should().Throw().WithMessage( @@ -86,11 +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" } - }; + var collection = new[] { new Customer { Age = 21, Name = "John" }, new Customer { Age = 22, Name = "Jane" } }; // Act / Assert collection.Should().SatisfyRespectively( @@ -106,6 +96,35 @@ public void When_asserting_collection_satisfies_all_inspectors_it_should_succeed }); } + private class Customer + { + private string PrivateProperty { get; set; } + + protected string ProtectedProperty { get; set; } + + public string Name { get; set; } + + public int Age { get; set; } + + public DateTime Birthdate { get; set; } + + public long Id { get; set; } + + public void SetProtected(string value) + { + ProtectedProperty = value; + } + + public Customer() + { + } + + public Customer(string privateProperty) + { + PrivateProperty = privateProperty; + } + } + private class CustomerWithItems : Customer { public int[] Items { get; set; } @@ -141,7 +160,7 @@ public void When_asserting_collection_does_not_satisfy_any_inspector_it_should_t // Assert act.Should().Throw().WithMessage( -@"Expected customers to satisfy all inspectors because we want to test nested assertions, but some inspectors are not satisfied: + @"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: @@ -154,7 +173,7 @@ public void When_asserting_collection_does_not_satisfy_any_inspector_it_should_t *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] @@ -178,11 +197,8 @@ public void When_inspectors_count_does_not_equal_asserting_collection_length_it_ // Act Action act = () => collection.Should().SatisfyRespectively( - new Action[] - { - value => value.Should().Be(1), - value => value.Should().Be(2) - }, "because we want to test the failure {0}", "message"); + new Action[] { value => value.Should().Be(1), value => value.Should().Be(2) }, + "because we want to test the failure {0}", "message"); // Assert act.Should().Throw().WithMessage( @@ -197,14 +213,12 @@ public void When_inspectors_count_does_not_equal_asserting_collection_length_it_ // Act Action act = () => collection.Should().SatisfyRespectively( - new Action[] - { - value => value.Should().Be(1), - }, "because we want to test the failure {0}", "message"); + new Action[] { value => value.Should().Be(1), }, "because we want to test the failure {0}", "message"); // Assert act.Should().Throw().WithMessage("*because we want to test the failure*"); } + #endregion } } diff --git a/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveConstructor.cs b/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveConstructor.cs index 839d842d3b..2c422ebf31 100644 --- a/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveConstructor.cs +++ b/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveConstructor.cs @@ -1,6 +1,5 @@ using System; using FluentAssertions.Common; -using FluentAssertions.Specs.Equivalency; using Xunit; using Xunit.Sdk; @@ -43,7 +42,7 @@ public void When_asserting_a_type_has_a_constructor_which_it_does_not_it_fails() // Assert act.Should().Throw() .WithMessage( - "Expected constructor *.ClassWithNoMembers(System.Int32, System.Type) to exist *failure message*" + + "Expected constructor *ClassWithNoMembers(System.Int32, System.Type) to exist *failure message*" + ", but it does not."); } diff --git a/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveDefaultConstructor.cs b/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveDefaultConstructor.cs index f8d1d9d9d7..7f4bd5b371 100644 --- a/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveDefaultConstructor.cs +++ b/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveDefaultConstructor.cs @@ -1,6 +1,5 @@ using System; using FluentAssertions.Common; -using FluentAssertions.Specs.Equivalency; using Xunit; using Xunit.Sdk; @@ -81,7 +80,7 @@ public void When_asserting_a_type_has_a_default_constructor_which_it_does_not_an // Assert act.Should().Throw() .WithMessage( - "Expected constructor *.ClassWithCctorAndNonDefaultConstructor() to exist *failure message*" + + "Expected constructor *ClassWithCctorAndNonDefaultConstructor() to exist *failure message*" + ", but it does not."); } @@ -147,7 +146,7 @@ public void When_asserting_a_type_does_not_have_a_default_constructor_which_it_d // Assert act.Should().Throw() - .WithMessage("Expected constructor *.ClassWithCctor*() not to exist *failure message*, but it does."); + .WithMessage("Expected constructor *ClassWithCctor*() not to exist *failure message*, but it does."); } [Fact] @@ -180,5 +179,21 @@ public void When_subject_is_null_not_have_default_constructor_should_fail() } #endregion + + internal class ClassWithNoMembers + { + } + + internal class ClassWithCctor + { + static ClassWithCctor() { } + } + + internal class ClassWithCctorAndNonDefaultConstructor + { + static ClassWithCctorAndNonDefaultConstructor() { } + + public ClassWithCctorAndNonDefaultConstructor(int _) { } + } } } diff --git a/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveIndexer.cs b/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveIndexer.cs index 5e09b70125..3b1fc79a81 100644 --- a/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveIndexer.cs +++ b/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveIndexer.cs @@ -1,6 +1,5 @@ using System; using FluentAssertions.Common; -using FluentAssertions.Specs.Equivalency; using Xunit; using Xunit.Sdk; @@ -45,7 +44,7 @@ public void When_asserting_a_type_has_an_indexer_which_it_does_not_it_fails() // Assert act.Should().Throw() .WithMessage( - "Expected String *.ClassWithNoMembers[System.Int32, System.Type] to exist *failure message*" + + "Expected String *ClassWithNoMembers[System.Int32, System.Type] to exist *failure message*" + ", but it does not."); } diff --git a/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveMethod.cs b/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveMethod.cs index 18a07e73a5..b92c96c8c6 100644 --- a/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveMethod.cs +++ b/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveMethod.cs @@ -1,6 +1,5 @@ using System; using FluentAssertions.Common; -using FluentAssertions.Specs.Equivalency; using Xunit; using Xunit.Sdk; @@ -45,7 +44,7 @@ public void When_asserting_a_type_has_a_method_which_it_does_not_it_fails() // Assert act.Should().Throw() .WithMessage( - "Expected method *.ClassWithNoMembers.NonExistentMethod(*.Int32, *.Type) to exist *failure message*" + + "Expected method *ClassWithNoMembers.NonExistentMethod(*.Int32, *.Type) to exist *failure message*" + ", but it does not."); } diff --git a/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveProperty.cs b/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveProperty.cs index 6be45932f3..44888e9cbd 100644 --- a/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveProperty.cs +++ b/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveProperty.cs @@ -1,6 +1,5 @@ using System; using FluentAssertions.Common; -using FluentAssertions.Specs.Equivalency; using Xunit; using Xunit.Sdk; @@ -43,7 +42,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 String *ClassWithNoMembers.PublicProperty to exist *failure message*, but it does not."); } [Fact]