Skip to content

Commit

Permalink
Merge pull request #8 from tomasbruckner/exclude-properties-from-diff
Browse files Browse the repository at this point in the history
#7 exclude properties from diff
tomasbruckner authored Feb 17, 2023
2 parents 46993df + 56e3322 commit 0c650d7
Showing 12 changed files with 318 additions and 43 deletions.
13 changes: 12 additions & 1 deletion JestDotnet/JestDotnet/Core/Settings/SnapshotSettings.cs
Original file line number Diff line number Diff line change
@@ -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
/// </summary>
public static readonly Func<StringWriter, JsonTextWriter> DefaultCreateTextWriter = stringWriter =>
new JsonTextWriter(stringWriter) {Formatting = Formatting.Indented};
new JsonTextWriter(stringWriter) { Formatting = Formatting.Indented };

/// <summary>
/// text writer creator
/// </summary>
public static Func<StringWriter, JsonTextWriter> CreateTextWriter = DefaultCreateTextWriter;

/// <summary>
/// default diff options creator
/// </summary>
public static readonly Func<JsonDiffOptions> DefaultCreateDiffOptions = () => null;

/// <summary>
/// diff options creator
/// </summary>
public static Func<JsonDiffOptions> CreateDiffOptions = DefaultCreateDiffOptions;
}
}
53 changes: 34 additions & 19 deletions JestDotnet/JestDotnet/Core/SnapshotComparer.cs
Original file line number Diff line number Diff line change
@@ -1,40 +1,55 @@
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;

namespace JestDotnet.Core
{
internal static class SnapshotComparer
{
internal static (bool IsValid, string Message) CompareSnapshots<T>(T expectedObject, T actualObject)
internal static (bool IsValid, string Message) CompareSnapshots<T>(
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<T>(
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());
}
}
}
12 changes: 7 additions & 5 deletions JestDotnet/JestDotnet/JestAssert.cs
Original file line number Diff line number Diff line change
@@ -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<bool, string>) SnapshotComparer.CompareSnapshots(snapshot, actual);
var (isValid, message) = (ValueTuple<bool, string>)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<bool, string>) SnapshotComparer.CompareSnapshots(inlineSnapshot, actual);
(ValueTuple<bool, string>)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<bool, string>) SnapshotComparer.CompareSnapshots(expected, actual);
var (isValid, message) = (ValueTuple<bool, string>)SnapshotComparer.CompareSnapshots(expected, actual, diffOptions);
if (!isValid)
{
throw new SnapshotMismatch(message);
12 changes: 6 additions & 6 deletions JestDotnet/JestDotnet/JestDotnet.csproj
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net47;netcoreapp2.1;netcoreapp3.1;net50;netstandard2.0;netstandard2.1</TargetFrameworks>
<Version>1.3.1</Version>
<TargetFrameworks>net6.0;net47;net50;netstandard2.0;netstandard2.1</TargetFrameworks>
<Version>1.4.0</Version>
<Authors>Tomas Bruckner</Authors>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<RepositoryUrl>https://github.com/tomasbruckner/jest-dotnet</RepositoryUrl>
@@ -27,13 +27,13 @@
</PropertyGroup>

<ItemGroup>
<None Include="..\jest.png" Pack="true" Visible="false" PackagePath=""/>
<None Include="..\jest.png" Pack="true" Visible="false" PackagePath="" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="JsonDiffPatch.Net" Version="2.3.0"/>
<PackageReference Include="Microsoft.CSharp" Version="4.7.0"/>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3"/>
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="SystemTextJson.JsonDiffPatch" Version="1.3.1" />
</ItemGroup>

</Project>
16 changes: 11 additions & 5 deletions JestDotnet/JestDotnet/JestDotnetExtensions.cs
Original file line number Diff line number Diff line change
@@ -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);
151 changes: 151 additions & 0 deletions JestDotnet/XUnitTests/ExcludePathsTests.cs
Original file line number Diff line number Diff line change
@@ -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<string, DictionaryChildObject>
{
{
"first", new DictionaryChildObject
{
IntValue = 312,
StringValue1 = "nested1",
StringValue2 = null,
IntNullValue = null,
ReadOnlyDictionaryChildren = new Dictionary<string, bool>
{
{ "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<string, bool>()
}
},
{
"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<string, Dictionary<string, int>>
{
{
"a", new Dictionary<string, int>
{
{ "exclude", invalidValue },
{ "notExclude", 15}
}
},
{
"exclude", new Dictionary<string, int>
{
{ "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"
});
}
}
}
2 changes: 1 addition & 1 deletion JestDotnet/XUnitTests/ExtensionTests.cs
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ public void ShouldMatchInlineSnapshot()
""FirstName"": ""John"",
""LastName"": ""Bam"",
""DateOfBirth"": ""2008-07-07T00:00:00"",
""Age"": 13,
""Age"": 13
}"
);
}
4 changes: 2 additions & 2 deletions JestDotnet/XUnitTests/SimpleTests.cs
Original file line number Diff line number Diff line change
@@ -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
}"
);
}
8 changes: 4 additions & 4 deletions JestDotnet/XUnitTests/XUnitTests.csproj
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net47;netcoreapp2.1;netcoreapp3.1;net50</TargetFrameworks>
<TargetFrameworks>net6.0;net47;net50;netstandard2.0;netstandard2.1</TargetFrameworks>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3"/>
<PackageReference Include="coverlet.collector" Version="1.3.0">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.0" />
<PackageReference Include="coverlet.collector" Version="3.1.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit" Version="2.4.1"/>
<PackageReference Include="xunit" Version="2.4.2" />
</ItemGroup>

<ItemGroup>
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"a": {
"exclude": -1,
"notExclude": 15
},
"exclude": {
"c": -1
}
}
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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`)

0 comments on commit 0c650d7

Please sign in to comment.