Skip to content

Commit f2bcd81

Browse files
authored
Bugfix for multiple field options within one Assert Match Option. (#168)
1 parent f55c872 commit f2bcd81

10 files changed

+443
-43
lines changed

src/Snapshooter/Core/JsonSnapshotComparer.cs

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,25 @@ public void CompareSnapshots(
5757
_snapshotAssert.Assert(expectedSnapshotToCompare, actualSnapshotToCompare);
5858
}
5959

60-
private void ExecuteFieldMatchActions(
60+
private static void ExecuteFieldMatchActions(
6161
JToken actualSnapshot,
6262
JToken expectedSnapshot,
6363
MatchOptions matchOptions)
6464
{
6565
try
6666
{
67+
List<FieldOption> fieldOptions = new List<FieldOption>();
68+
6769
foreach (FieldMatchOperator matchOperator in matchOptions.MatchOperators)
6870
{
6971
FieldOption fieldOption = matchOperator
7072
.ExecuteMatch(actualSnapshot, expectedSnapshot);
7173

74+
fieldOptions.Add(fieldOption);
75+
}
76+
77+
foreach (FieldOption fieldOption in fieldOptions)
78+
{
7279
RemoveFieldFromSnapshot(fieldOption, actualSnapshot);
7380
RemoveFieldFromSnapshot(fieldOption, expectedSnapshot);
7481
}
@@ -98,22 +105,27 @@ private static void RemoveFieldFromSnapshot(FieldOption fieldOption, JToken snap
98105
$"match options are not allowed.");
99106
}
100107

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

105-
if (actualTokens is { })
112+
RemoveFields(actualTokens);
113+
}
114+
}
115+
116+
private static void RemoveFields(IEnumerable<JToken> actualTokens)
117+
{
118+
if (actualTokens is { })
119+
{
120+
foreach (JToken actual in actualTokens.ToList())
106121
{
107-
foreach (JToken actual in actualTokens.ToList())
122+
if (actual.Parent is JArray array)
123+
{
124+
array.Remove(actual);
125+
}
126+
else
108127
{
109-
if (actual.Parent is JArray array)
110-
{
111-
array.Remove(actual);
112-
}
113-
else
114-
{
115-
actual.Parent?.Remove();
116-
}
128+
actual.Parent?.Remove();
117129
}
118130
}
119131
}

src/Snapshooter/Core/MatchOperators/AcceptMatchOperator.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public override FieldOption ExecuteMatch(JToken snapshotData, JToken expectedSna
4848
return fieldOption;
4949
}
5050

51-
private JToken FormatField(JToken field)
51+
private void FormatField(JToken field)
5252
{
5353
string originalValue = string.Empty;
5454
if (_keepOriginalValue)
@@ -65,13 +65,11 @@ private JToken FormatField(JToken field)
6565
originalValue = $"(original: '{fieldValue}')";
6666
}
6767

68-
_fields.Add(field.Path, FieldOption.ConvertToType<object>(field));
68+
_fields.Add(field.Path, field.ConvertToType<object>());
6969

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

7272
field.Replace(new JValue($"AcceptAny<{typeAlias}>{originalValue}"));
73-
74-
return field;
7573
}
7674

7775
private void VerifyFieldType(string path, object field)

src/Snapshooter/Extensions/JTokenExtensions.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,23 @@ public static string ConvertToValueString(this JToken jtoken)
2525
})
2626
.Trim('\"');
2727
}
28+
29+
/// <summary>
30+
/// Converts a JToken to a specified type.
31+
/// </summary>
32+
/// <typeparam name="T">The type to convert to.</typeparam>
33+
/// <param name="field">The JToken field to convert.</param>
34+
/// <returns>The converted value.</returns>
35+
public static T ConvertToType<T>(this JToken field)
36+
{
37+
if (typeof(T) == typeof(int))
38+
{
39+
// This is a workaround, because the json method ToObject<> rounds
40+
// decimal values to integer values, which is wrong.
41+
return JsonConvert.DeserializeObject<T>(field.Value<string>());
42+
}
43+
44+
return field.ToObject<T>();
45+
}
2846
}
2947
}

src/Snapshooter/FieldOption.cs

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4-
using Newtonsoft.Json;
54
using Newtonsoft.Json.Linq;
65
using Snapshooter.Exceptions;
76
using Snapshooter.Extensions;
@@ -14,7 +13,8 @@ namespace Snapshooter
1413
/// </summary>
1514
public class FieldOption
1615
{
17-
private JToken _snapshotData;
16+
private readonly JToken _snapshotData;
17+
private readonly List<string> _fieldPaths;
1818

1919
/// <summary>
2020
/// Constructor of the class <see cref="FieldOption"/>
@@ -24,12 +24,13 @@ public class FieldOption
2424
public FieldOption(JToken snapshotData)
2525
{
2626
_snapshotData = snapshotData;
27+
_fieldPaths = new List<string>();
2728
}
2829

2930
/// <summary>
3031
/// The path of the field, which was requested.
3132
/// </summary>
32-
public string[] FieldPaths { get; private set; }
33+
public string[] FieldPaths => _fieldPaths.ToArray();
3334

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

97-
T fieldValue = ConvertToType<T>(fields.Single());
98+
T fieldValue = fields.Single().ConvertToType<T>();
9899

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

120121
T[] fieldValues = fields
121-
.Select(f => ConvertToType<T>(f))
122+
.Select(field => field.ConvertToType<T>())
122123
.ToArray();
123124

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

146147
T[] fieldValues = properties
147-
.Select(jprop => ConvertToType<T>(jprop.Value))
148+
.Select(jprop => jprop.Value.ConvertToType<T>())
148149
.ToArray();
149150

150151
return fieldValues;
@@ -178,16 +179,15 @@ private JProperty[] GetPropertiesByName(string name)
178179
.Where(jprop => jprop.Name == name)
179180
.ToArray();
180181

181-
FieldPaths = properties
182-
.Select(jprop => jprop.Path)
183-
.ToArray();
182+
_fieldPaths.AddRange(
183+
properties.Select(jprop => jprop.Path)) ;
184184

185185
return properties;
186186
}
187187

188188
private JToken[] GetTokensByPath(string fieldPath)
189189
{
190-
FieldPaths = new[] { fieldPath };
190+
_fieldPaths.Add(fieldPath);
191191

192192
if (_snapshotData is JValue)
193193
{
@@ -206,18 +206,6 @@ private JToken[] GetTokensByPath(string fieldPath)
206206
}
207207

208208
return jTokens.ToArray();
209-
}
210-
211-
public static T ConvertToType<T>(JToken field)
212-
{
213-
if (typeof(T) == typeof(int))
214-
{
215-
// This is a workaround, because the json method ToObject<> rounds
216-
// decimal values to integer values, which is wrong.
217-
return JsonConvert.DeserializeObject<T>(field.Value<string>());
218-
}
219-
220-
return field.ToObject<T>();
221-
}
209+
}
222210
}
223211
}

test/Snapshooter.Xunit.Tests/Asynchronous/SnapshotTests.Asynchronous.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Threading.Tasks;
33
using Snapshooter.Exceptions;
44
using Snapshooter.Tests.Data;
@@ -117,13 +117,13 @@ public async Task Match_FactMatchSnapshotInAsncMethod_SuccessfulMatch()
117117
public async Task Match_FactMatchSnapshotInAsncMethodWithImplcName_SuccessfulMatch()
118118
{
119119
// arrange
120+
Snapshot.FullName();
121+
120122
await Task.Delay(1);
121123

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

124-
await Task.Delay(1);
125-
126-
Snapshot.FullName();
126+
await Task.Delay(1);
127127

128128
// act
129129
await AsyncMatchWithImplicitFullName(testPerson);

test/Snapshooter.Xunit.Tests/MatchOptions/AssertField/AssertFieldTests.cs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using FluentAssertions;
44
using Snapshooter.Exceptions;
55
using Snapshooter.Tests.Data;
6+
using Snapshooter.Xunit.Tests.Helpers;
67
using Xunit;
78

89
namespace Snapshooter.Xunit.Tests.MatchOptions.AssertField
@@ -238,5 +239,94 @@ public void Match_AssertEqualGuidValueFailsWithinFirstSnapshotCreation_ThrowsSna
238239
Assert.Throws<SnapshotCompareException>(action);
239240
Assert.False(File.Exists(snapshotFileName));
240241
}
242+
243+
[Fact]
244+
public void Match_AssertTwoFieldsAgainstEachOtherWithinSnapshot_SuccessfulAssert()
245+
{
246+
// arrange
247+
TestPerson testChild = TestDataBuilder
248+
.TestPersonMarkWalton()
249+
.Build();
250+
251+
// act & assert
252+
Snapshot.Match(testChild,
253+
matchOption => matchOption.Assert(fieldOption =>
254+
Assert.Equal(
255+
fieldOption.Field<string>("Address.Country.Name"),
256+
fieldOption.Field<string>("Relatives[0].Address.Country.Name"))));
257+
}
258+
259+
[Fact]
260+
public void Match_AssertTwoUnequalFieldsAgainstEachOtherWithinSnapshot_FailedAssert()
261+
{
262+
// arrange
263+
TestPerson testChild = TestDataBuilder
264+
.TestPersonMarkWalton()
265+
.Build();
266+
267+
// act
268+
Action action = () => Snapshot.Match(testChild,
269+
matchOption => matchOption.Assert(fieldOption =>
270+
Assert.Equal(
271+
fieldOption.Field<string>("Lastname"),
272+
fieldOption.Field<string>("Relatives[0].Lastname"))));
273+
274+
// assert
275+
Assert.Throws<SnapshotCompareException>(action);
276+
}
277+
278+
[Fact]
279+
public void Match_AssertTwoRandomFieldsAgainstEachOtherWithinSnapshot_SuccessfulAssert()
280+
{
281+
// arrange
282+
TestPerson testPerson = TestDataBuilder
283+
.TestPersonMarkWalton()
284+
.Build();
285+
286+
Guid id = Guid.NewGuid();
287+
288+
testPerson.Id = id;
289+
testPerson.Relatives[0].Id = id;
290+
291+
// act & assert
292+
Snapshot.Match(testPerson,
293+
matchOption => matchOption.Assert(fieldOption =>
294+
Assert.Equal(
295+
fieldOption.Field<string>("Id"),
296+
fieldOption.Field<string>("Relatives[0].Id"))));
297+
}
298+
299+
[Fact]
300+
public void Match_AssertMultipleTwoFieldCompares_Success()
301+
{
302+
// arrange
303+
string snapshotFileName =
304+
SnapshotDefaultNameResolver.ResolveSnapshotDefaultName();
305+
306+
string expectedSnapshot =
307+
File.ReadAllText(snapshotFileName + ".original");
308+
309+
// act & assert
310+
Snapshot.Match(expectedSnapshot, matchOptions => matchOptions
311+
.Assert(fieldOption => Assert.Equal(
312+
fieldOption.Field<Guid>("changeSets[0].DocumentInstanceId"),
313+
fieldOption.Field<Guid>("docInstances[0].Id")))
314+
.Assert(fieldOption => Assert.Equal(
315+
fieldOption.Field<Guid>("audits[0].DocumentInstanceId"),
316+
fieldOption.Field<Guid>("docInstances[0].Id")))
317+
.Assert(fieldOption => Assert.Equal(
318+
fieldOption.Field<Guid>("changeSets[0].UserId"),
319+
fieldOption.Field<Guid>("users[0].UserId")))
320+
.Assert(fieldOption => Assert.Equal(
321+
fieldOption.Field<Guid>("users[0].UserId"),
322+
fieldOption.Field<Guid>("audits[0].UserId")))
323+
.IsTypeFields<Guid>("changeSets[*].UserId")
324+
.IsTypeField<Guid>("changeSets[*].Id")
325+
.IsTypeField<DateTime>("changeSets[*].ChangeDate")
326+
.IsTypeField<Guid>("changeSets[*].DocumentInstanceId")
327+
.IsTypeField<DateTime>("audits[*].TimeStamp")
328+
.IsTypeField<Guid>("audits[*].Id")
329+
);
330+
}
241331
}
242332
}

0 commit comments

Comments
 (0)