From d3aa99582eeb7d2b95c07bf2efff6920eac829bb Mon Sep 17 00:00:00 2001 From: Ronald Kroon Date: Wed, 5 Feb 2020 22:25:10 +0100 Subject: [PATCH 1/2] Improve error message structure for BeEquivalentTo Changed sentence to avoid confusion of _actual_ document after "Expected" --- Src/FluentAssertions.Json/JTokenAssertions.cs | 4 ++-- .../JTokenAssertionsSpecs.cs | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Src/FluentAssertions.Json/JTokenAssertions.cs b/Src/FluentAssertions.Json/JTokenAssertions.cs index a30dc8a7..10f030c8 100644 --- a/Src/FluentAssertions.Json/JTokenAssertions.cs +++ b/Src/FluentAssertions.Json/JTokenAssertions.cs @@ -89,9 +89,9 @@ public AndConstraint BeEquivalentTo(JToken expected, string be Difference difference = JTokenDifferentiator.FindFirstDifference(Subject, expected); var message = $"JSON document {difference?.ToString().Escape(true)}.{Environment.NewLine}" + - $"Expected{Environment.NewLine}" + + $"Actual document{Environment.NewLine}" + $"{Format(Subject, true).Escape(true)}{Environment.NewLine}" + - $"to be equivalent to{Environment.NewLine}" + + $"was expected to be equivalent to{Environment.NewLine}" + $"{Format(expected, true).Escape(true)}{Environment.NewLine}" + "{reason}."; diff --git a/Tests/FluentAssertions.Json.Shared.Specs/JTokenAssertionsSpecs.cs b/Tests/FluentAssertions.Json.Shared.Specs/JTokenAssertionsSpecs.cs index 854086ab..b5c7b0bc 100644 --- a/Tests/FluentAssertions.Json.Shared.Specs/JTokenAssertionsSpecs.cs +++ b/Tests/FluentAssertions.Json.Shared.Specs/JTokenAssertionsSpecs.cs @@ -150,9 +150,9 @@ public void When_objects_differ_BeEquivalentTo_should_fail() var expectedMessage = $"JSON document {expectedDifference}." + - $"Expected" + + $"Actual document" + $"{Format(actual, true)}" + - $"to be equivalent to" + + $"was expected to be equivalent to" + $"{Format(expected, true)}."; //----------------------------------------------------------------------------------------------------------- @@ -192,9 +192,9 @@ public void When_properties_differ_BeEquivalentTo_should_fail() var expectedMessage = $"JSON document {expectedDifference}." + - $"Expected" + + $"Actual document" + $"{Format(actual, true)}" + - $"to be equivalent to" + + $"was expected to be equivalent to" + $"{Format(expected, true)}."; //----------------------------------------------------------------------------------------------------------- @@ -366,9 +366,9 @@ public void When_specifying_a_reason_why_object_should_be_equivalent_it_should_u var expectedMessage = $"JSON document misses property $.child.expected." + - $"Expected" + + $"Actual document" + $"{Format(subject, true)}" + - $"to be equivalent to" + + $"was expected to be equivalent to" + $"{Format(expected, true)} " + "because we want to test the failure message."; @@ -444,9 +444,9 @@ public void When_properties_contains_curly_braces_BeEquivalentTo_should_not_fail var expectedMessage = "JSON document has a different value at $.{a1}.b." + - "Expected" + + "Actual document" + $"{Format(actual, true)}" + - "to be equivalent to" + + "was expected to be equivalent to" + $"{Format(expected, true)}."; //----------------------------------------------------------------------------------------------------------- From f7dfdf955c8a368ff6bdc762b17ed7ed7c77784b Mon Sep 17 00:00:00 2001 From: Ronald Kroon Date: Fri, 7 Feb 2020 23:03:42 +0100 Subject: [PATCH 2/2] Make BeEquivalentTo error messages more precise Mention actual type and length differences in message --- FluentAssertions.Json.sln.DotSettings | 1 + .../JTokenDifferentiator.cs | 70 ++++++-- .../JTokenAssertionsSpecs.cs | 149 ++++++++++-------- 3 files changed, 142 insertions(+), 78 deletions(-) diff --git a/FluentAssertions.Json.sln.DotSettings b/FluentAssertions.Json.sln.DotSettings index 1293109c..d0c4c588 100644 --- a/FluentAssertions.Json.sln.DotSettings +++ b/FluentAssertions.Json.sln.DotSettings @@ -62,6 +62,7 @@ True True True + True True True True diff --git a/Src/FluentAssertions.Json/JTokenDifferentiator.cs b/Src/FluentAssertions.Json/JTokenDifferentiator.cs index 2dda4050..37031b50 100644 --- a/Src/FluentAssertions.Json/JTokenDifferentiator.cs +++ b/Src/FluentAssertions.Json/JTokenDifferentiator.cs @@ -50,7 +50,7 @@ private static Difference FindJArrayDifference(JArray actualArray, JToken expect { if (!(expected is JArray expectedArray)) { - return new Difference(DifferenceKind.OtherType, path); + return new Difference(DifferenceKind.OtherType, path, Describe(actualArray.Type), Describe(expected.Type)); } return CompareItems(actualArray, expectedArray, path); @@ -63,7 +63,7 @@ private static Difference CompareItems(JArray actual, JArray expected, JPath pat if (actualChildren.Count() != expectedChildren.Count()) { - return new Difference(DifferenceKind.DifferentLength, path); + return new Difference(DifferenceKind.DifferentLength, path, actualChildren.Count(), expectedChildren.Count()); } for (int i = 0; i < actualChildren.Count(); i++) @@ -84,7 +84,7 @@ private static Difference FindJObjectDifference(JObject actual, JToken expected, { if (!(expected is JObject expectedObject)) { - return new Difference(DifferenceKind.OtherType, path); + return new Difference(DifferenceKind.OtherType, path, Describe(actual.Type), Describe(expected.Type)); } return CompareProperties(actual?.Properties(), expectedObject.Properties(), path); @@ -131,7 +131,7 @@ private static Difference FindJPropertyDifference(JProperty actualProperty, JTok { if (!(expected is JProperty expectedProperty)) { - return new Difference(DifferenceKind.OtherType, path); + return new Difference(DifferenceKind.OtherType, path, Describe(actualProperty.Type), Describe(expected.Type)); } if (actualProperty.Name != expectedProperty.Name) @@ -146,7 +146,7 @@ private static Difference FindValueDifference(JValue actualValue, JToken expecte { if (!(expected is JValue expectedValue)) { - return new Difference(DifferenceKind.OtherType, path); + return new Difference(DifferenceKind.OtherType, path, Describe(actualValue.Type), Describe(expected.Type)); } return CompareValues(actualValue, expectedValue, path); @@ -156,7 +156,7 @@ private static Difference CompareValues(JValue actual, JValue expected, JPath pa { if (actual.Type != expected.Type) { - return new Difference(DifferenceKind.OtherType, path); + return new Difference(DifferenceKind.OtherType, path, Describe(actual.Type), Describe(expected.Type)); } if (!actual.Equals(expected)) @@ -166,10 +166,61 @@ private static Difference CompareValues(JValue actual, JValue expected, JPath pa return null; } + + private static string Describe(JTokenType jTokenType) + { + switch (jTokenType) + { + case JTokenType.None: + return "type none"; + case JTokenType.Object: + return "an object"; + case JTokenType.Array: + return "an array"; + case JTokenType.Constructor: + return "a constructor"; + case JTokenType.Property: + return "a property"; + case JTokenType.Comment: + return "a comment"; + case JTokenType.Integer: + return "an integer"; + case JTokenType.Float: + return "a float"; + case JTokenType.String: + return "a string"; + case JTokenType.Boolean: + return "a boolean"; + case JTokenType.Null: + return "type null"; + case JTokenType.Undefined: + return "type undefined"; + case JTokenType.Date: + return "a date"; + case JTokenType.Raw: + return "type raw"; + case JTokenType.Bytes: + return "type bytes"; + case JTokenType.Guid: + return "a GUID"; + case JTokenType.Uri: + return "a URI"; + case JTokenType.TimeSpan: + return "a timespan"; + default: + throw new ArgumentOutOfRangeException(nameof(jTokenType), jTokenType, null); + } + } } internal class Difference { + public Difference(DifferenceKind kind, JPath path, object actual, object expected) : this(kind, path) + { + Actual = actual; + Expected = expected; + } + public Difference(DifferenceKind kind, JPath path) { Kind = kind; @@ -177,8 +228,9 @@ public Difference(DifferenceKind kind, JPath path) } private DifferenceKind Kind { get; } - private JPath Path { get; } + private object Actual { get; } + private object Expected { get; } public override string ToString() { @@ -189,13 +241,13 @@ public override string ToString() case DifferenceKind.ExpectedIsNull: return "is not null"; case DifferenceKind.OtherType: - return $"has a different type at {Path}"; + return $"has {Actual} instead of {Expected} at {Path}"; case DifferenceKind.OtherName: return $"has a different name at {Path}"; case DifferenceKind.OtherValue: return $"has a different value at {Path}"; case DifferenceKind.DifferentLength: - return $"has a different length at {Path}"; + return $"has {Actual} elements instead of {Expected} at {Path}"; case DifferenceKind.ActualMissesProperty: return $"misses property {Path}"; case DifferenceKind.ExpectedMissesProperty: diff --git a/Tests/FluentAssertions.Json.Shared.Specs/JTokenAssertionsSpecs.cs b/Tests/FluentAssertions.Json.Shared.Specs/JTokenAssertionsSpecs.cs index b5c7b0bc..7b289e95 100644 --- a/Tests/FluentAssertions.Json.Shared.Specs/JTokenAssertionsSpecs.cs +++ b/Tests/FluentAssertions.Json.Shared.Specs/JTokenAssertionsSpecs.cs @@ -65,103 +65,114 @@ public void When_both_objects_are_the_same_or_equal_BeEquivalentTo_should_succee a.Should().BeEquivalentTo(b); } - [Fact] - public void When_objects_differ_BeEquivalentTo_should_fail() + public static IEnumerable FailingBeEquivalentCases { - //----------------------------------------------------------------------------------------------------------- - // Arrange - //----------------------------------------------------------------------------------------------------------- - var testCases = new [] + get { - Tuple.Create( - (string)null, + yield return new object[] + { + null, "{ id: 2 }", - "is null") - , - Tuple.Create( + "is null" + }; + yield return new object[] + { "{ id: 1 }", - (string)null, - "is not null") - , - Tuple.Create( + null, + "is not null" + }; + yield return new object[] + { "{ items: [] }", "{ items: 2 }", - "has a different type at $.items") - , - Tuple.Create( + "has an array instead of an integer at $.items" + }; + yield return new object[] + { "{ items: [ \"fork\", \"knife\" , \"spoon\" ]}", "{ items: [ \"fork\", \"knife\" ]}", - "has a different length at $.items") - , - Tuple.Create( + "has 3 elements instead of 2 at $.items" + }; + yield return new object[] + { "{ items: [ \"fork\", \"knife\" ]}", "{ items: [ \"fork\", \"knife\" , \"spoon\" ]}", - "has a different length at $.items") - , - Tuple.Create( + "has 2 elements instead of 3 at $.items" + }; + yield return new object[] + { "{ items: [ \"fork\", \"knife\" , \"spoon\" ]}", "{ items: [ \"fork\", \"spoon\", \"knife\" ]}", - "has a different value at $.items[1]") - , - Tuple.Create( + "has a different value at $.items[1]" + }; + yield return new object[] + { "{ tree: { } }", "{ tree: \"oak\" }", - "has a different type at $.tree") - , - Tuple.Create( + "has an object instead of a string at $.tree" + }; + yield return new object[] + { "{ tree: { leaves: 10} }", "{ tree: { branches: 5, leaves: 10 } }", - "misses property $.tree.branches") - , - Tuple.Create( + "misses property $.tree.branches" + }; + yield return new object[] + { "{ tree: { branches: 5, leaves: 10 } }", "{ tree: { leaves: 10} }", - "has extra property $.tree.branches") - , - Tuple.Create( + "has extra property $.tree.branches" + }; + yield return new object[] + { "{ tree: { leaves: 5 } }", "{ tree: { leaves: 10} }", - "has a different value at $.tree.leaves") - , - Tuple.Create( + "has a different value at $.tree.leaves" + }; + yield return new object[] + { "{ eyes: \"blue\" }", "{ eyes: [] }", - "has a different type at $.eyes") - , - Tuple.Create( + "has a string instead of an array at $.eyes" + }; + yield return new object[] + { "{ eyes: \"blue\" }", "{ eyes: 2 }", - "has a different type at $.eyes") - , - Tuple.Create( + "has a string instead of an integer at $.eyes" + }; + yield return new object[] + { "{ id: 1 }", "{ id: 2 }", - "has a different value at $.id") - }; - - foreach (var testCase in testCases) - { - string actualJson = testCase.Item1; - string expectedJson = testCase.Item2; - string expectedDifference = testCase.Item3; + "has a different value at $.id" + }; + } + } - var actual = (actualJson != null) ? JToken.Parse(actualJson) : null; - var expected = (expectedJson != null) ? JToken.Parse(expectedJson) : null; + [Theory, MemberData(nameof(FailingBeEquivalentCases))] + public void When_objects_are_not_equivalent_it_should_throw(string actualJson, string expectedJson, + string expectedDifference) + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + var actual = (actualJson != null) ? JToken.Parse(actualJson) : null; + var expected = (expectedJson != null) ? JToken.Parse(expectedJson) : null; - var expectedMessage = - $"JSON document {expectedDifference}." + - $"Actual document" + - $"{Format(actual, true)}" + - $"was expected to be equivalent to" + - $"{Format(expected, true)}."; + var expectedMessage = + $"JSON document {expectedDifference}." + + $"Actual document" + + $"{Format(actual, true)}" + + $"was expected to be equivalent to" + + $"{Format(expected, true)}."; - //----------------------------------------------------------------------------------------------------------- - // Act & Assert - //----------------------------------------------------------------------------------------------------------- - actual.Should().Invoking(x => x.BeEquivalentTo(expected)) - .Should().Throw() - .WithMessage(expectedMessage); - } + //----------------------------------------------------------------------------------------------------------- + // Act & Assert + //----------------------------------------------------------------------------------------------------------- + actual.Should().Invoking(x => x.BeEquivalentTo(expected)) + .Should().Throw() + .WithMessage(expectedMessage); } [Fact] @@ -175,7 +186,7 @@ public void When_properties_differ_BeEquivalentTo_should_fail() Tuple.Create( new JProperty("eyes", "blue"), new JArray(), - "has a different type at $") + "has a property instead of an array at $") , Tuple.Create( new JProperty("eyes", "blue"),