Skip to content

Commit 957ff74

Browse files
author
sam.segers
committed
add ContainSubtree that takes a config
1 parent 45e9db1 commit 957ff74

File tree

2 files changed

+113
-0
lines changed

2 files changed

+113
-0
lines changed

Src/FluentAssertions.Json/JTokenAssertions.cs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,49 @@ public AndConstraint<JTokenAssertions> ContainSubtree(string subtree, string bec
431431
return ContainSubtree(subtreeToken, because, becauseArgs);
432432
}
433433

434+
/// <summary>
435+
/// Recursively asserts that the current <see cref="JToken"/> contains at least the properties or elements of the specified <paramref name="subtree"/>.
436+
/// </summary>
437+
/// <param name="subtree">The subtree to search for</param>
438+
/// <param name="config">The options to consider while asserting values</param>
439+
/// <param name="because">
440+
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
441+
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
442+
/// </param>
443+
/// <param name="becauseArgs">
444+
/// Zero or more objects to format using the placeholders in <paramref name="because"/>.
445+
/// </param>
446+
/// <remarks>Use this method to match the current <see cref="JToken"/> against an arbitrary subtree,
447+
/// permitting it to contain any additional properties or elements. This way we can test multiple properties on a <see cref="JObject"/> at once,
448+
/// or test if a <see cref="JArray"/> contains any items that match a set of properties, assert that a JSON document has a given shape, etc. </remarks>
449+
/// <example>
450+
/// This example asserts the values of multiple properties of a child object within a JSON document using a specified double precision.
451+
/// <code>
452+
/// var json = JToken.Parse("{ success: true, data: { id: 123, type: 'my-type', value: 0.99 } }");
453+
/// json.Should().ContainSubtree(JToken.Parse("{ success: true, data: { type: 'my-type', value: 1.0 } }"), options => options
454+
/// .Using&lt;double&gt;(d => d.Subject.Should().BeApproximately(d.Expectation, 1e-1))
455+
/// .WhenTypeIs&lt;double&gt;());
456+
/// </code>
457+
/// </example>
458+
/// <example>
459+
/// This example asserts that a <see cref="JArray"/> within a <see cref="JObject"/> has at least one element with at least the given properties, using a specified double precision.
460+
/// <code>
461+
/// var json = JToken.Parse("{ id: 1, items: [ { id: 2, type: 'my-type', value: 0.99 }, { id: 3, type: 'other-type', value: 3 } ] }");
462+
/// json.Should().ContainSubtree("{ items: [ { type: 'my-type', value: 1 } ] }", options => options
463+
/// .Using&lt;double&gt;(d => d.Subject.Should().BeApproximately(d.Expectation, 1e-1))
464+
/// .WhenTypeIs&lt;double&gt;());
465+
/// </code>
466+
/// </example>
467+
public AndConstraint<JTokenAssertions> ContainSubtree(string subtree,
468+
Func<IJsonAssertionOptions<object>, IJsonAssertionOptions<object>> config,
469+
string because = "",
470+
params object[] becauseArgs)
471+
{
472+
JToken subtreeToken = Parse(subtree, nameof(subtree));
473+
474+
return BeEquivalentTo(subtreeToken, true, config, because, becauseArgs);
475+
}
476+
434477
/// <summary>
435478
/// Recursively asserts that the current <see cref="JToken"/> contains at least the properties or elements of the specified <paramref name="subtree"/>.
436479
/// </summary>
@@ -464,6 +507,47 @@ public AndConstraint<JTokenAssertions> ContainSubtree(JToken subtree, string bec
464507
return BeEquivalentTo(subtree, true, options => options, because, becauseArgs);
465508
}
466509

510+
/// <summary>
511+
/// Recursively asserts that the current <see cref="JToken"/> contains at least the properties or elements of the specified <paramref name="subtree"/>.
512+
/// </summary>
513+
/// <param name="subtree">The subtree to search for</param>
514+
/// <param name="config">The options to consider while asserting values</param>
515+
/// <param name="because">
516+
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
517+
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
518+
/// </param>
519+
/// <param name="becauseArgs">
520+
/// Zero or more objects to format using the placeholders in <paramref name="because"/>.
521+
/// </param>
522+
/// <remarks>Use this method to match the current <see cref="JToken"/> against an arbitrary subtree,
523+
/// permitting it to contain any additional properties or elements. This way we can test multiple properties on a <see cref="JObject"/> at once,
524+
/// or test if a <see cref="JArray"/> contains any items that match a set of properties, assert that a JSON document has a given shape, etc. </remarks>
525+
/// <example>
526+
/// This example asserts the values of multiple properties of a child object within a JSON document, using a specified double precision.
527+
/// <code>
528+
/// var json = JToken.Parse("{ success: true, data: { id: 123, type: 'my-type', value: 0.99 } }");
529+
/// json.Should().ContainSubtree(JToken.Parse("{ success: true, data: { type: 'my-type', value: 1.0 } }"), options => options
530+
/// .Using&lt;double&gt;(d => d.Subject.Should().BeApproximately(d.Expectation, 1e-1))
531+
/// .WhenTypeIs&lt;double&gt;());
532+
/// </code>
533+
/// </example>
534+
/// <example>
535+
/// This example asserts that a <see cref="JArray"/> within a <see cref="JObject"/> has at least one element with at least the given properties, using a specified double precision.
536+
/// <code>
537+
/// var json = JToken.Parse("{ id: 1, items: [ { id: 2, type: 'my-type', value: 0.99 }, { id: 3, type: 'other-type', value: 3 } ] }");
538+
/// json.Should().ContainSubtree(JToken.Parse("{ items: [ { type: 'my-type', value: 1 } ] }"), options => options
539+
/// .Using&lt;double&gt;(d => d.Subject.Should().BeApproximately(d.Expectation, 1e-1))
540+
/// .WhenTypeIs&lt;double&gt;());
541+
/// </code>
542+
/// </example>
543+
public AndConstraint<JTokenAssertions> ContainSubtree(JToken subtree,
544+
Func<IJsonAssertionOptions<object>, IJsonAssertionOptions<object>> config,
545+
string because = "",
546+
params object[] becauseArgs)
547+
{
548+
return BeEquivalentTo(subtree, true, config, because, becauseArgs);
549+
}
550+
467551
private static JToken Parse(string json, string paramName)
468552
{
469553
try

Tests/FluentAssertions.Json.Specs/JTokenAssertionsSpecs.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,35 @@ public void When_checking_subtree_with_an_invalid_expected_string_it_should_prov
947947
.WithInnerException<JsonReaderException>();
948948
}
949949

950+
[Fact]
951+
public void Assert_property_with_approximation_succeeds()
952+
{
953+
// Arrange
954+
var actual = JToken.Parse("{ \"id\": 1.1232 }");
955+
var expected = JToken.Parse("{ \"id\": 1.1235 }");
956+
957+
// Act & Assert
958+
actual.Should().ContainSubtree(expected, options => options
959+
.Using<double>(d => d.Subject.Should().BeApproximately(d.Expectation, 1e-3))
960+
.WhenTypeIs<double>());
961+
}
962+
963+
[Fact]
964+
public void Can_assert_on_a_field_with_approximation()
965+
{
966+
// Arrange
967+
var actual = JToken.Parse("{ \"id\": 1.1232 }");
968+
var expected = JToken.Parse("{ \"id\": 1.1235 }");
969+
970+
// Act & Assert
971+
actual.Should().
972+
Invoking(x => x.ContainSubtree(expected, options => options
973+
.Using<double>(d => d.Subject.Should().BeApproximately(d.Expectation, 1e-5))
974+
.WhenTypeIs<double>()))
975+
.Should().Throw<XunitException>()
976+
.WithMessage("JSON document has a different value at $.id.*");
977+
}
978+
950979
#endregion
951980

952981
private static string Format(JToken value, bool useLineBreaks = false)

0 commit comments

Comments
 (0)