Skip to content

Commit

Permalink
Bugfix for multiple field options within one Assert Match Option. (#168)
Browse files Browse the repository at this point in the history
  • Loading branch information
nscheibe authored Nov 18, 2022
1 parent f55c872 commit f2bcd81
Show file tree
Hide file tree
Showing 10 changed files with 443 additions and 43 deletions.
36 changes: 24 additions & 12 deletions src/Snapshooter/Core/JsonSnapshotComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,25 @@ public void CompareSnapshots(
_snapshotAssert.Assert(expectedSnapshotToCompare, actualSnapshotToCompare);
}

private void ExecuteFieldMatchActions(
private static void ExecuteFieldMatchActions(
JToken actualSnapshot,
JToken expectedSnapshot,
MatchOptions matchOptions)
{
try
{
List<FieldOption> fieldOptions = new List<FieldOption>();

foreach (FieldMatchOperator matchOperator in matchOptions.MatchOperators)
{
FieldOption fieldOption = matchOperator
.ExecuteMatch(actualSnapshot, expectedSnapshot);

fieldOptions.Add(fieldOption);
}

foreach (FieldOption fieldOption in fieldOptions)
{
RemoveFieldFromSnapshot(fieldOption, actualSnapshot);
RemoveFieldFromSnapshot(fieldOption, expectedSnapshot);
}
Expand Down Expand Up @@ -98,22 +105,27 @@ private static void RemoveFieldFromSnapshot(FieldOption fieldOption, JToken snap
$"match options are not allowed.");
}

foreach (var fieldPath in fieldOption.FieldPaths ?? new string[] { })
foreach (var fieldPath in fieldOption.FieldPaths ?? Array.Empty<string>())
{
IEnumerable<JToken> actualTokens = snapshot.SelectTokens(fieldPath, false);

if (actualTokens is { })
RemoveFields(actualTokens);
}
}

private static void RemoveFields(IEnumerable<JToken> actualTokens)
{
if (actualTokens is { })
{
foreach (JToken actual in actualTokens.ToList())
{
foreach (JToken actual in actualTokens.ToList())
if (actual.Parent is JArray array)
{
array.Remove(actual);
}
else
{
if (actual.Parent is JArray array)
{
array.Remove(actual);
}
else
{
actual.Parent?.Remove();
}
actual.Parent?.Remove();
}
}
}
Expand Down
6 changes: 2 additions & 4 deletions src/Snapshooter/Core/MatchOperators/AcceptMatchOperator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public override FieldOption ExecuteMatch(JToken snapshotData, JToken expectedSna
return fieldOption;
}

private JToken FormatField(JToken field)
private void FormatField(JToken field)
{
string originalValue = string.Empty;
if (_keepOriginalValue)
Expand All @@ -65,13 +65,11 @@ private JToken FormatField(JToken field)
originalValue = $"(original: '{fieldValue}')";
}

_fields.Add(field.Path, FieldOption.ConvertToType<object>(field));
_fields.Add(field.Path, field.ConvertToType<object>());

string typeAlias = typeof(T).GetAliasName();

field.Replace(new JValue($"AcceptAny<{typeAlias}>{originalValue}"));

return field;
}

private void VerifyFieldType(string path, object field)
Expand Down
18 changes: 18 additions & 0 deletions src/Snapshooter/Extensions/JTokenExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,23 @@ public static string ConvertToValueString(this JToken jtoken)
})
.Trim('\"');
}

/// <summary>
/// Converts a JToken to a specified type.
/// </summary>
/// <typeparam name="T">The type to convert to.</typeparam>
/// <param name="field">The JToken field to convert.</param>
/// <returns>The converted value.</returns>
public static T ConvertToType<T>(this JToken field)
{
if (typeof(T) == typeof(int))
{
// This is a workaround, because the json method ToObject<> rounds
// decimal values to integer values, which is wrong.
return JsonConvert.DeserializeObject<T>(field.Value<string>());
}

return field.ToObject<T>();
}
}
}
34 changes: 11 additions & 23 deletions src/Snapshooter/FieldOption.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Snapshooter.Exceptions;
using Snapshooter.Extensions;
Expand All @@ -14,7 +13,8 @@ namespace Snapshooter
/// </summary>
public class FieldOption
{
private JToken _snapshotData;
private readonly JToken _snapshotData;
private readonly List<string> _fieldPaths;

/// <summary>
/// Constructor of the class <see cref="FieldOption"/>
Expand All @@ -24,12 +24,13 @@ public class FieldOption
public FieldOption(JToken snapshotData)
{
_snapshotData = snapshotData;
_fieldPaths = new List<string>();
}

/// <summary>
/// The path of the field, which was requested.
/// </summary>
public string[] FieldPaths { get; private set; }
public string[] FieldPaths => _fieldPaths.ToArray();

/// <summary>
/// Finds all jtokens by the given field path. if the field path
Expand Down Expand Up @@ -94,7 +95,7 @@ public T Field<T>(string fieldPath)
$"Please use the FieldOption for fields array (Fields).");
}

T fieldValue = ConvertToType<T>(fields.Single());
T fieldValue = fields.Single().ConvertToType<T>();

return fieldValue;
}
Expand All @@ -118,7 +119,7 @@ public T[] Fields<T>(string fieldPath)
IEnumerable<JToken> fields = GetTokensByPath(fieldPath);

T[] fieldValues = fields
.Select(f => ConvertToType<T>(f))
.Select(field => field.ConvertToType<T>())
.ToArray();

return fieldValues;
Expand All @@ -144,7 +145,7 @@ public T[] GetAllFieldsByName<T>(string name)
GetPropertiesByName(name);

T[] fieldValues = properties
.Select(jprop => ConvertToType<T>(jprop.Value))
.Select(jprop => jprop.Value.ConvertToType<T>())
.ToArray();

return fieldValues;
Expand Down Expand Up @@ -178,16 +179,15 @@ private JProperty[] GetPropertiesByName(string name)
.Where(jprop => jprop.Name == name)
.ToArray();

FieldPaths = properties
.Select(jprop => jprop.Path)
.ToArray();
_fieldPaths.AddRange(
properties.Select(jprop => jprop.Path)) ;

return properties;
}

private JToken[] GetTokensByPath(string fieldPath)
{
FieldPaths = new[] { fieldPath };
_fieldPaths.Add(fieldPath);

if (_snapshotData is JValue)
{
Expand All @@ -206,18 +206,6 @@ private JToken[] GetTokensByPath(string fieldPath)
}

return jTokens.ToArray();
}

public static T ConvertToType<T>(JToken field)
{
if (typeof(T) == typeof(int))
{
// This is a workaround, because the json method ToObject<> rounds
// decimal values to integer values, which is wrong.
return JsonConvert.DeserializeObject<T>(field.Value<string>());
}

return field.ToObject<T>();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Threading.Tasks;
using Snapshooter.Exceptions;
using Snapshooter.Tests.Data;
Expand Down Expand Up @@ -117,13 +117,13 @@ public async Task Match_FactMatchSnapshotInAsncMethod_SuccessfulMatch()
public async Task Match_FactMatchSnapshotInAsncMethodWithImplcName_SuccessfulMatch()
{
// arrange
Snapshot.FullName();

await Task.Delay(1);

TestPerson testPerson = TestDataBuilder.TestPersonSandraSchneider().Build();

await Task.Delay(1);

Snapshot.FullName();
await Task.Delay(1);

// act
await AsyncMatchWithImplicitFullName(testPerson);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using FluentAssertions;
using Snapshooter.Exceptions;
using Snapshooter.Tests.Data;
using Snapshooter.Xunit.Tests.Helpers;
using Xunit;

namespace Snapshooter.Xunit.Tests.MatchOptions.AssertField
Expand Down Expand Up @@ -238,5 +239,94 @@ public void Match_AssertEqualGuidValueFailsWithinFirstSnapshotCreation_ThrowsSna
Assert.Throws<SnapshotCompareException>(action);
Assert.False(File.Exists(snapshotFileName));
}

[Fact]
public void Match_AssertTwoFieldsAgainstEachOtherWithinSnapshot_SuccessfulAssert()
{
// arrange
TestPerson testChild = TestDataBuilder
.TestPersonMarkWalton()
.Build();

// act & assert
Snapshot.Match(testChild,
matchOption => matchOption.Assert(fieldOption =>
Assert.Equal(
fieldOption.Field<string>("Address.Country.Name"),
fieldOption.Field<string>("Relatives[0].Address.Country.Name"))));
}

[Fact]
public void Match_AssertTwoUnequalFieldsAgainstEachOtherWithinSnapshot_FailedAssert()
{
// arrange
TestPerson testChild = TestDataBuilder
.TestPersonMarkWalton()
.Build();

// act
Action action = () => Snapshot.Match(testChild,
matchOption => matchOption.Assert(fieldOption =>
Assert.Equal(
fieldOption.Field<string>("Lastname"),
fieldOption.Field<string>("Relatives[0].Lastname"))));

// assert
Assert.Throws<SnapshotCompareException>(action);
}

[Fact]
public void Match_AssertTwoRandomFieldsAgainstEachOtherWithinSnapshot_SuccessfulAssert()
{
// arrange
TestPerson testPerson = TestDataBuilder
.TestPersonMarkWalton()
.Build();

Guid id = Guid.NewGuid();

testPerson.Id = id;
testPerson.Relatives[0].Id = id;

// act & assert
Snapshot.Match(testPerson,
matchOption => matchOption.Assert(fieldOption =>
Assert.Equal(
fieldOption.Field<string>("Id"),
fieldOption.Field<string>("Relatives[0].Id"))));
}

[Fact]
public void Match_AssertMultipleTwoFieldCompares_Success()
{
// arrange
string snapshotFileName =
SnapshotDefaultNameResolver.ResolveSnapshotDefaultName();

string expectedSnapshot =
File.ReadAllText(snapshotFileName + ".original");

// act & assert
Snapshot.Match(expectedSnapshot, matchOptions => matchOptions
.Assert(fieldOption => Assert.Equal(
fieldOption.Field<Guid>("changeSets[0].DocumentInstanceId"),
fieldOption.Field<Guid>("docInstances[0].Id")))
.Assert(fieldOption => Assert.Equal(
fieldOption.Field<Guid>("audits[0].DocumentInstanceId"),
fieldOption.Field<Guid>("docInstances[0].Id")))
.Assert(fieldOption => Assert.Equal(
fieldOption.Field<Guid>("changeSets[0].UserId"),
fieldOption.Field<Guid>("users[0].UserId")))
.Assert(fieldOption => Assert.Equal(
fieldOption.Field<Guid>("users[0].UserId"),
fieldOption.Field<Guid>("audits[0].UserId")))
.IsTypeFields<Guid>("changeSets[*].UserId")
.IsTypeField<Guid>("changeSets[*].Id")
.IsTypeField<DateTime>("changeSets[*].ChangeDate")
.IsTypeField<Guid>("changeSets[*].DocumentInstanceId")
.IsTypeField<DateTime>("audits[*].TimeStamp")
.IsTypeField<Guid>("audits[*].Id")
);
}
}
}
Loading

0 comments on commit f2bcd81

Please sign in to comment.