From 339e78c5e18303cd710b9386f554451e74e6f94f Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Wed, 13 Apr 2022 18:47:24 +0100 Subject: [PATCH 1/2] Make JsonTestHelpers.AssertJsonEqual print contextual error messages --- .../tests/Common/JsonTestHelper.cs | 73 +++++++++++++++---- 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/src/libraries/System.Text.Json/tests/Common/JsonTestHelper.cs b/src/libraries/System.Text.Json/tests/Common/JsonTestHelper.cs index 1f1c05c9a8aece..c6c3448d50f413 100644 --- a/src/libraries/System.Text.Json/tests/Common/JsonTestHelper.cs +++ b/src/libraries/System.Text.Json/tests/Common/JsonTestHelper.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Text.Json.Serialization.Tests; using System.Text.Json.Serialization.Tests.Schemas.OrderPayload; using System.Text.RegularExpressions; @@ -17,59 +18,99 @@ public static void AssertJsonEqual(string expected, string actual) { using JsonDocument expectedDom = JsonDocument.Parse(expected); using JsonDocument actualDom = JsonDocument.Parse(actual); - AssertJsonEqual(expectedDom.RootElement, actualDom.RootElement); + AssertJsonEqual(expectedDom.RootElement, actualDom.RootElement, new()); } - private static void AssertJsonEqual(JsonElement expected, JsonElement actual) + private static void AssertJsonEqual(JsonElement expected, JsonElement actual, Stack path) { JsonValueKind valueKind = expected.ValueKind; - Assert.Equal(valueKind, actual.ValueKind); + AssertTrue(valueKind == actual.ValueKind); switch (valueKind) { case JsonValueKind.Object: - var propertyNames = new HashSet(); - + var expectedProperties = new List(); foreach (JsonProperty property in expected.EnumerateObject()) { - propertyNames.Add(property.Name); + expectedProperties.Add(property.Name); } + var actualProperties = new List(); foreach (JsonProperty property in actual.EnumerateObject()) { - propertyNames.Add(property.Name); + actualProperties.Add(property.Name); } - foreach (string name in propertyNames) + foreach (var property in expectedProperties.Except(actualProperties)) { - AssertJsonEqual(expected.GetProperty(name), actual.GetProperty(name)); + AssertTrue(false, $"Property \"{property}\" missing from actual object."); + } + + foreach (var property in actualProperties.Except(expectedProperties)) + { + AssertTrue(false, $"Actual object defines additional property \"{property}\"."); + } + + foreach (string name in expectedProperties) + { + path.Push(name); + AssertJsonEqual(expected.GetProperty(name), actual.GetProperty(name), path); + path.Pop(); } break; case JsonValueKind.Array: - JsonElement.ArrayEnumerator expectedEnumerator = actual.EnumerateArray(); - JsonElement.ArrayEnumerator actualEnumerator = expected.EnumerateArray(); + JsonElement.ArrayEnumerator expectedEnumerator = expected.EnumerateArray(); + JsonElement.ArrayEnumerator actualEnumerator = actual.EnumerateArray(); + int i = 0; while (expectedEnumerator.MoveNext()) { - Assert.True(actualEnumerator.MoveNext()); - AssertJsonEqual(expectedEnumerator.Current, actualEnumerator.Current); + AssertTrue(actualEnumerator.MoveNext(), "Actual array contains fewer elements."); + path.Push(i++); + AssertJsonEqual(expectedEnumerator.Current, actualEnumerator.Current, path); + path.Pop(); } - Assert.False(actualEnumerator.MoveNext()); + AssertTrue(!actualEnumerator.MoveNext(), "Actual array contains additional elements."); break; case JsonValueKind.String: - Assert.Equal(expected.GetString(), actual.GetString()); + AssertTrue(expected.GetString() == actual.GetString()); break; case JsonValueKind.Number: case JsonValueKind.True: case JsonValueKind.False: case JsonValueKind.Null: - Assert.Equal(expected.GetRawText(), actual.GetRawText()); + AssertTrue(expected.GetRawText() == actual.GetRawText()); break; default: Debug.Fail($"Unexpected JsonValueKind: JsonValueKind.{valueKind}."); break; } + + void AssertTrue(bool condition, string? message = null) + { + if (!condition) + { + message ??= "Expected JSON does not match actual value"; + Assert.Fail($"{message}\nExpected JSON: {expected}\n Actual JSON: {actual}\n in JsonPath: {BuildJsonPath(path)}"); + } + + // TODO replace with JsonPath implementation for JsonElement + // cf. https://github.com/dotnet/runtime/issues/31068 + static string BuildJsonPath(Stack path) + { + var sb = new StringBuilder("$"); + foreach (object node in path.Reverse()) + { + string pathNode = node is string propertyName + ? "." + propertyName + : $"[{(int)node}]"; + + sb.Append(pathNode); + } + return sb.ToString(); + } + } } public static async Task> ToListAsync(this IAsyncEnumerable source) From 340d90a2a7b6938e5ab1311ae123859edac1298b Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Fri, 15 Apr 2022 16:45:27 +0100 Subject: [PATCH 2/2] address feedback --- .../tests/Common/JsonTestHelper.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/libraries/System.Text.Json/tests/Common/JsonTestHelper.cs b/src/libraries/System.Text.Json/tests/Common/JsonTestHelper.cs index c6c3448d50f413..45c3428b498092 100644 --- a/src/libraries/System.Text.Json/tests/Common/JsonTestHelper.cs +++ b/src/libraries/System.Text.Json/tests/Common/JsonTestHelper.cs @@ -24,7 +24,7 @@ public static void AssertJsonEqual(string expected, string actual) private static void AssertJsonEqual(JsonElement expected, JsonElement actual, Stack path) { JsonValueKind valueKind = expected.ValueKind; - AssertTrue(valueKind == actual.ValueKind); + AssertTrue(passCondition: valueKind == actual.ValueKind); switch (valueKind) { @@ -43,12 +43,12 @@ private static void AssertJsonEqual(JsonElement expected, JsonElement actual, St foreach (var property in expectedProperties.Except(actualProperties)) { - AssertTrue(false, $"Property \"{property}\" missing from actual object."); + AssertTrue(passCondition: false, $"Property \"{property}\" missing from actual object."); } foreach (var property in actualProperties.Except(expectedProperties)) { - AssertTrue(false, $"Actual object defines additional property \"{property}\"."); + AssertTrue(passCondition: false, $"Actual object defines additional property \"{property}\"."); } foreach (string name in expectedProperties) @@ -65,31 +65,31 @@ private static void AssertJsonEqual(JsonElement expected, JsonElement actual, St int i = 0; while (expectedEnumerator.MoveNext()) { - AssertTrue(actualEnumerator.MoveNext(), "Actual array contains fewer elements."); + AssertTrue(passCondition: actualEnumerator.MoveNext(), "Actual array contains fewer elements."); path.Push(i++); AssertJsonEqual(expectedEnumerator.Current, actualEnumerator.Current, path); path.Pop(); } - AssertTrue(!actualEnumerator.MoveNext(), "Actual array contains additional elements."); + AssertTrue(passCondition: !actualEnumerator.MoveNext(), "Actual array contains additional elements."); break; case JsonValueKind.String: - AssertTrue(expected.GetString() == actual.GetString()); + AssertTrue(passCondition: expected.GetString() == actual.GetString()); break; case JsonValueKind.Number: case JsonValueKind.True: case JsonValueKind.False: case JsonValueKind.Null: - AssertTrue(expected.GetRawText() == actual.GetRawText()); + AssertTrue(passCondition: expected.GetRawText() == actual.GetRawText()); break; default: Debug.Fail($"Unexpected JsonValueKind: JsonValueKind.{valueKind}."); break; } - void AssertTrue(bool condition, string? message = null) + void AssertTrue(bool passCondition, string? message = null) { - if (!condition) + if (!passCondition) { message ??= "Expected JSON does not match actual value"; Assert.Fail($"{message}\nExpected JSON: {expected}\n Actual JSON: {actual}\n in JsonPath: {BuildJsonPath(path)}");