From 56e33220ec10dc90febe8c06698d24e7f7ec966f Mon Sep 17 00:00:00 2001 From: Tomas Bruckner Date: Fri, 17 Feb 2023 15:33:53 +0100 Subject: [PATCH] exclude properties from diff --- .../Core/Settings/SnapshotSettings.cs | 13 +- .../JestDotnet/Core/SnapshotComparer.cs | 53 +++--- JestDotnet/JestDotnet/JestAssert.cs | 12 +- JestDotnet/JestDotnet/JestDotnet.csproj | 12 +- JestDotnet/JestDotnet/JestDotnetExtensions.cs | 16 +- JestDotnet/XUnitTests/ExcludePathsTests.cs | 151 ++++++++++++++++++ JestDotnet/XUnitTests/ExtensionTests.cs | 2 +- JestDotnet/XUnitTests/SimpleTests.cs | 4 +- JestDotnet/XUnitTests/XUnitTests.csproj | 8 +- ...ExcludePathsTestsShouldIgnoreIntValue.snap | 42 +++++ ...ePathsTestsShouldIgnoreSimpleIntValue.snap | 9 ++ README.md | 39 +++++ 12 files changed, 318 insertions(+), 43 deletions(-) create mode 100644 JestDotnet/XUnitTests/ExcludePathsTests.cs create mode 100644 JestDotnet/XUnitTests/__snapshots__/ExcludePathsTestsShouldIgnoreIntValue.snap create mode 100644 JestDotnet/XUnitTests/__snapshots__/ExcludePathsTestsShouldIgnoreSimpleIntValue.snap diff --git a/JestDotnet/JestDotnet/Core/Settings/SnapshotSettings.cs b/JestDotnet/JestDotnet/Core/Settings/SnapshotSettings.cs index 7cb330a..789453d 100644 --- a/JestDotnet/JestDotnet/Core/Settings/SnapshotSettings.cs +++ b/JestDotnet/JestDotnet/Core/Settings/SnapshotSettings.cs @@ -1,6 +1,7 @@ using System; using System.Globalization; using System.IO; +using System.Text.Json.JsonDiffPatch; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -85,11 +86,21 @@ public static class SnapshotSettings /// default text writer creator /// public static readonly Func DefaultCreateTextWriter = stringWriter => - new JsonTextWriter(stringWriter) {Formatting = Formatting.Indented}; + new JsonTextWriter(stringWriter) { Formatting = Formatting.Indented }; /// /// text writer creator /// public static Func CreateTextWriter = DefaultCreateTextWriter; + + /// + /// default diff options creator + /// + public static readonly Func DefaultCreateDiffOptions = () => null; + + /// + /// diff options creator + /// + public static Func CreateDiffOptions = DefaultCreateDiffOptions; } } diff --git a/JestDotnet/JestDotnet/Core/SnapshotComparer.cs b/JestDotnet/JestDotnet/Core/SnapshotComparer.cs index 0d7f0e5..7ac36eb 100644 --- a/JestDotnet/JestDotnet/Core/SnapshotComparer.cs +++ b/JestDotnet/JestDotnet/Core/SnapshotComparer.cs @@ -1,4 +1,7 @@ -using JsonDiffPatchDotNet; +using System; +using System.Text.Json.JsonDiffPatch; +using System.Text.Json.Nodes; +using JestDotnet.Core.Settings; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -6,35 +9,47 @@ namespace JestDotnet.Core { internal static class SnapshotComparer { - internal static (bool IsValid, string Message) CompareSnapshots(T expectedObject, T actualObject) + internal static (bool IsValid, string Message) CompareSnapshots( + T expectedObject, + T actualObject, + JsonDiffOptions diffOptions = null + ) { - var serializedExpectedObject = JsonConvert.SerializeObject(expectedObject); - var actualSerializedObject = JsonConvert.SerializeObject(actualObject); - var isValid = serializedExpectedObject == actualSerializedObject; - var message = isValid ? "" : "fail"; - - return (isValid, message); + return Diff( + JsonConvert.SerializeObject(expectedObject), + JsonConvert.SerializeObject(actualObject), + diffOptions + ); } internal static (bool IsValid, string Message) CompareSnapshots( string serializedExpectedObject, - T actualObject + T actualObject, + JsonDiffOptions diffOptions = null ) { - var expectedToken = JToken.Parse(serializedExpectedObject); - var actualToken = JToken.FromObject(actualObject); - var isValid = JToken.DeepEquals(expectedToken, actualToken); - var message = isValid ? "" : GetDiff(expectedToken, actualToken); - - return (isValid, message); + return Diff( + serializedExpectedObject, + JsonConvert.SerializeObject(actualObject), + diffOptions + ); } - internal static string GetDiff(JToken expectedToken, JToken actualToken) + internal static (bool IsValid, string Message) Diff( + string expected, + string actual, + JsonDiffOptions diffOptions = null + ) { - var diff = new JsonDiffPatch(); - var patch = diff.Diff(expectedToken, actualToken); + var expectedNode = JsonNode.Parse(expected); + var actualNode = JsonNode.Parse(actual); + + var diff = expectedNode.Diff( + actualNode, + diffOptions ?? SnapshotSettings.DefaultCreateDiffOptions() + ); - return patch.ToString(); + return (diff == null, diff?.ToJsonString()); } } } diff --git a/JestDotnet/JestDotnet/JestAssert.cs b/JestDotnet/JestDotnet/JestAssert.cs index 9131902..6604ae6 100644 --- a/JestDotnet/JestDotnet/JestAssert.cs +++ b/JestDotnet/JestDotnet/JestAssert.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.CompilerServices; +using System.Text.Json.JsonDiffPatch; using JestDotnet.Core; using JestDotnet.Core.Exceptions; using JestDotnet.Core.Settings; @@ -11,6 +12,7 @@ public static class JestAssert public static void ShouldMatchSnapshot( object actual, string hint = "", + JsonDiffOptions diffOptions = null, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "" ) @@ -24,26 +26,26 @@ public static void ShouldMatchSnapshot( return; } - var (isValid, message) = (ValueTuple) SnapshotComparer.CompareSnapshots(snapshot, actual); + var (isValid, message) = (ValueTuple)SnapshotComparer.CompareSnapshots(snapshot, actual, diffOptions); if (!isValid) { SnapshotUpdater.TryUpdateSnapshot(path, actual, message); } } - public static void ShouldMatchInlineSnapshot(dynamic actual, string inlineSnapshot) + public static void ShouldMatchInlineSnapshot(dynamic actual, string inlineSnapshot, JsonDiffOptions diffOptions = null) { var (isValid, message) = - (ValueTuple) SnapshotComparer.CompareSnapshots(inlineSnapshot, actual); + (ValueTuple)SnapshotComparer.CompareSnapshots(inlineSnapshot, actual, diffOptions); if (!isValid) { throw new SnapshotMismatch(message); } } - public static void ShouldMatchObject(dynamic actual, dynamic expected) + public static void ShouldMatchObject(dynamic actual, dynamic expected, JsonDiffOptions diffOptions = null) { - var (isValid, message) = (ValueTuple) SnapshotComparer.CompareSnapshots(expected, actual); + var (isValid, message) = (ValueTuple)SnapshotComparer.CompareSnapshots(expected, actual, diffOptions); if (!isValid) { throw new SnapshotMismatch(message); diff --git a/JestDotnet/JestDotnet/JestDotnet.csproj b/JestDotnet/JestDotnet/JestDotnet.csproj index 2eb24ad..2ff9865 100644 --- a/JestDotnet/JestDotnet/JestDotnet.csproj +++ b/JestDotnet/JestDotnet/JestDotnet.csproj @@ -1,8 +1,8 @@ - net47;netcoreapp2.1;netcoreapp3.1;net50;netstandard2.0;netstandard2.1 - 1.3.1 + net6.0;net47;net50;netstandard2.0;netstandard2.1 + 1.4.0 Tomas Bruckner true https://github.com/tomasbruckner/jest-dotnet @@ -27,13 +27,13 @@ - + - - - + + + diff --git a/JestDotnet/JestDotnet/JestDotnetExtensions.cs b/JestDotnet/JestDotnet/JestDotnetExtensions.cs index 97378d5..0675d8f 100644 --- a/JestDotnet/JestDotnet/JestDotnetExtensions.cs +++ b/JestDotnet/JestDotnet/JestDotnetExtensions.cs @@ -1,4 +1,5 @@ using System.Runtime.CompilerServices; +using System.Text.Json.JsonDiffPatch; using JestDotnet.Core; using JestDotnet.Core.Exceptions; using JestDotnet.Core.Settings; @@ -10,6 +11,7 @@ public static class JestDotnetExtensions public static void ShouldMatchSnapshot( this object actual, string hint = "", + JsonDiffOptions diffOptions = null, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "" ) @@ -23,25 +25,29 @@ public static void ShouldMatchSnapshot( return; } - var (isValid, message) = SnapshotComparer.CompareSnapshots(snapshot, actual); + var (isValid, message) = SnapshotComparer.CompareSnapshots(snapshot, actual, diffOptions); if (!isValid) { SnapshotUpdater.TryUpdateSnapshot(path, actual, message); } } - public static void ShouldMatchInlineSnapshot(this object actual, string inlineSnapshot) + public static void ShouldMatchInlineSnapshot( + this object actual, + string inlineSnapshot, + JsonDiffOptions diffOptions = null + ) { - var (isValid, message) = SnapshotComparer.CompareSnapshots(inlineSnapshot, actual); + var (isValid, message) = SnapshotComparer.CompareSnapshots(inlineSnapshot, actual, diffOptions); if (!isValid) { throw new SnapshotMismatch(message); } } - public static void ShouldMatchObject(this object actual, object expected) + public static void ShouldMatchObject(this object actual, object expected, JsonDiffOptions diffOptions = null) { - var (isValid, message) = SnapshotComparer.CompareSnapshots(expected, actual); + var (isValid, message) = SnapshotComparer.CompareSnapshots(expected, actual, diffOptions); if (!isValid) { throw new SnapshotMismatch(message); diff --git a/JestDotnet/XUnitTests/ExcludePathsTests.cs b/JestDotnet/XUnitTests/ExcludePathsTests.cs new file mode 100644 index 0000000..f8f9740 --- /dev/null +++ b/JestDotnet/XUnitTests/ExcludePathsTests.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.JsonDiffPatch; +using JestDotnet; +using Xunit; +using XUnitTests.Helpers; + +namespace XUnitTests +{ + public class ExcludePathsTests + { + [Fact] + public void ShouldIgnoreIntValue() + { + const int invalidValue = -1; + + var testObject = new ComplexObject + { + BoolValue = false, + IntValue = invalidValue, + StringValue = string.Empty, + ChildObject = new ChildObject + { + IntValue = 33, + StringValue = "child" + }, + DateTimeValue = DateTime.MaxValue, + IntNullValue = null, + Children = new Dictionary + { + { + "first", new DictionaryChildObject + { + IntValue = 312, + StringValue1 = "nested1", + StringValue2 = null, + IntNullValue = null, + ReadOnlyDictionaryChildren = new Dictionary + { + { "key1", false }, + { "key2", true }, + { + "very.very.very.very.very.very.very.very.very.very.very.very.very.long.key1", + true + }, + { "Рикроллинг", true }, + { + "very.very.very.very.very.very.very.very.very.very.very.very.very.long.key3", + true + }, + { + "very.very.very.very.very.very.very.very.very.very.very.very.very.long.key4", + true + }, + { "4", true } + } + } + }, + { + "second", new DictionaryChildObject + { + IntValue = 312, + StringValue1 = "nested2", + StringValue2 = "x", + IntNullValue = 4, + ReadOnlyDictionaryChildren = new Dictionary() + } + }, + { + "third", new DictionaryChildObject + { + IntValue = invalidValue, + StringValue1 = "nested2", + StringValue2 = "x", + IntNullValue = 4, + ReadOnlyDictionaryChildren = null + } + } + } + }; + + JestAssert.ShouldMatchSnapshot( + testObject, + "", + new JsonDiffOptions + { + PropertyFilter = (s, _) => s != "IntValue", + } + ); + } + + [Fact] + public void ShouldIgnoreSimpleIntValue() + { + const int invalidValue = -1; + const int validValue = 2; + + var testObject = new Dictionary> + { + { + "a", new Dictionary + { + { "exclude", invalidValue }, + { "notExclude", 15} + } + }, + { + "exclude", new Dictionary + { + { "c", invalidValue } + } + } + }; + + JestAssert.ShouldMatchSnapshot( + testObject, + "", + new JsonDiffOptions + { + JsonElementComparison = JsonElementComparison.Semantic, + PropertyFilter = (s, _) => s != "exclude", + } + ); + } + + [Fact] + public void ShouldIgnoreSimpleIntValue_Object() + { + var actual = new Person + { + Age = 13, + DateOfBirth = new DateTime(2008, 7, 7), + FirstName = "John", + LastName = "Bam" + }; + + var expected = new Person + { + Age = 13, + DateOfBirth = new DateTime(2008, 7, 7), + FirstName = "John", + LastName = "" + }; + + JestAssert.ShouldMatchObject(actual,expected, new JsonDiffOptions + { + PropertyFilter = (s, context) => s != "LastName" + }); + } + } +} diff --git a/JestDotnet/XUnitTests/ExtensionTests.cs b/JestDotnet/XUnitTests/ExtensionTests.cs index a7bd614..7fac090 100644 --- a/JestDotnet/XUnitTests/ExtensionTests.cs +++ b/JestDotnet/XUnitTests/ExtensionTests.cs @@ -25,7 +25,7 @@ public void ShouldMatchInlineSnapshot() ""FirstName"": ""John"", ""LastName"": ""Bam"", ""DateOfBirth"": ""2008-07-07T00:00:00"", - ""Age"": 13, + ""Age"": 13 }" ); } diff --git a/JestDotnet/XUnitTests/SimpleTests.cs b/JestDotnet/XUnitTests/SimpleTests.cs index 3a58ae5..e9f6639 100644 --- a/JestDotnet/XUnitTests/SimpleTests.cs +++ b/JestDotnet/XUnitTests/SimpleTests.cs @@ -99,7 +99,7 @@ public void ShouldMatchInlineDynamicSnapshot() ""FirstName"": ""John"", ""LastName"": ""Bam"", ""DateOfBirth"": ""2008-07-07T00:00:00"", - ""Age"": 13, + ""Age"": 13 }" ); } @@ -122,7 +122,7 @@ public void ShouldMatchInlineSnapshot() ""FirstName"": ""John"", ""LastName"": ""Bam"", ""DateOfBirth"": ""2008-07-07T00:00:00"", - ""Age"": 13, + ""Age"": 13 }" ); } diff --git a/JestDotnet/XUnitTests/XUnitTests.csproj b/JestDotnet/XUnitTests/XUnitTests.csproj index 133f907..768ed14 100644 --- a/JestDotnet/XUnitTests/XUnitTests.csproj +++ b/JestDotnet/XUnitTests/XUnitTests.csproj @@ -1,17 +1,17 @@ - net47;netcoreapp2.1;netcoreapp3.1;net50 + net6.0;net47;net50;netstandard2.0;netstandard2.1 false - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/JestDotnet/XUnitTests/__snapshots__/ExcludePathsTestsShouldIgnoreIntValue.snap b/JestDotnet/XUnitTests/__snapshots__/ExcludePathsTestsShouldIgnoreIntValue.snap new file mode 100644 index 0000000..55c88d0 --- /dev/null +++ b/JestDotnet/XUnitTests/__snapshots__/ExcludePathsTestsShouldIgnoreIntValue.snap @@ -0,0 +1,42 @@ +{ + "Children": { + "first": { + "ReadOnlyDictionaryChildren": { + "key1": false, + "key2": true, + "very.very.very.very.very.very.very.very.very.very.very.very.very.long.key1": true, + "Рикроллинг": true, + "very.very.very.very.very.very.very.very.very.very.very.very.very.long.key3": true, + "very.very.very.very.very.very.very.very.very.very.very.very.very.long.key4": true, + "4": true + }, + "IntValue": 312, + "StringValue1": "nested1", + "StringValue2": null, + "IntNullValue": null + }, + "second": { + "ReadOnlyDictionaryChildren": {}, + "IntValue": 312, + "StringValue1": "nested2", + "StringValue2": "x", + "IntNullValue": 4 + }, + "third": { + "ReadOnlyDictionaryChildren": null, + "IntValue": 312, + "StringValue1": "nested2", + "StringValue2": "x", + "IntNullValue": 4 + } + }, + "ChildObject": { + "IntValue": 33, + "StringValue": "child" + }, + "BoolValue": false, + "IntValue": 123, + "DateTimeValue": "9999-12-31T23:59:59.9999999", + "StringValue": "", + "IntNullValue": null +} diff --git a/JestDotnet/XUnitTests/__snapshots__/ExcludePathsTestsShouldIgnoreSimpleIntValue.snap b/JestDotnet/XUnitTests/__snapshots__/ExcludePathsTestsShouldIgnoreSimpleIntValue.snap new file mode 100644 index 0000000..5dadaee --- /dev/null +++ b/JestDotnet/XUnitTests/__snapshots__/ExcludePathsTestsShouldIgnoreSimpleIntValue.snap @@ -0,0 +1,9 @@ +{ + "a": { + "exclude": -1, + "notExclude": 15 + }, + "exclude": { + "c": -1 + } +} \ No newline at end of file diff --git a/README.md b/README.md index 3422b97..bd020f4 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,45 @@ JestAssert.ShouldMatchObject(actual,expected); ``` ## Advanced +### Excluding properties +If you want to exclude some properties from the diff, you can use `SnapshotSettings` class to specify your own + +* diffing options (use `SnapshotSettings.CreateDiffOptions`) + +Example: +```csharp +SnapshotSettings.CreateDiffOptions = () => new JsonDiffOptions +{ + PropertyFilter = (s, context) => s != "LastName" +}; +``` +or pass `JsonDiffOptions` as optional argument + +```csharp +var actual = new Person +{ + Age = 13, + DateOfBirth = new DateTime(2008, 7, 7), + FirstName = "John", + LastName = "Bam" +}; + +var expected = new Person +{ + Age = 13, + DateOfBirth = new DateTime(2008, 7, 7), + FirstName = "John", + LastName = "" +}; + +// this does not throw an exception and the test completes successfully +// property "LastName" is ignored from the diff +JestAssert.ShouldMatchObject(actual,expected, new JsonDiffOptions +{ + PropertyFilter = (s, context) => s != "LastName" +}); +``` + ### Configuring directory and file extensions If you need to configure it, you can use `SnapshotSettings` class to specify your own * extension instead of `.snap` (use `SnapshotSettings.SnapshotExtension`)