Skip to content

add ContainSubtree that takes a config #79

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion Src/FluentAssertions.Json/JTokenAssertions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ static JTokenAssertions()
/// Initializes a new instance of the <see cref="JTokenAssertions" /> class.
/// </summary>
/// <param name="subject">The subject</param>
/// <param name="orCreate"></param>
/// <param name="assertionChain">The assertion chain</param>
public JTokenAssertions(JToken subject, AssertionChain assertionChain)
: base(subject, assertionChain)
{
Expand Down Expand Up @@ -496,6 +496,41 @@ public AndConstraint<JTokenAssertions> ContainSubtree(JToken subtree, string bec
return BeEquivalentTo(subtree, true, options => options, because, becauseArgs);
}

/// <summary>
/// Recursively asserts that the current <see cref="JToken"/> contains at least the properties or elements of the specified <paramref name="subtree"/>.
/// </summary>
/// <param name="subtree">The subtree to search for</param>
/// <param name="config">The options to consider while asserting values</param>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <see cref="because" />.
/// </param>
/// <remarks>Use this method to match the current <see cref="JToken"/> against an arbitrary subtree,
/// permitting it to contain any additional properties or elements. This way we can test multiple properties on a <see cref="JObject"/> at once,
/// 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>
/// <example>
/// This example asserts the values of multiple properties of a child object within a JSON document.
/// <code>
/// var json = JToken.Parse("{ success: true, data: { id: 123, type: 'my-type', name: 'Noone' } }");
/// json.Should().ContainSubtree(JToken.Parse("{ success: true, data: { type: 'my-type', name: 'Noone' } }"));
/// </code>
/// </example>
/// <example>This example asserts that a <see cref="JArray"/> within a <see cref="JObject"/> has at least one element with at least the given properties</example>
/// <code>
/// var json = JToken.Parse("{ id: 1, items: [ { id: 2, type: 'my-type', name: 'Alpha' }, { id: 3, type: 'other-type', name: 'Bravo' } ] }");
/// json.Should().ContainSubtree(JToken.Parse("{ items: [ { type: 'my-type', name: 'Alpha' } ] }"));
/// </code>
public AndConstraint<JTokenAssertions> ContainSubtree(JToken subtree,
Func<IJsonAssertionOptions<object>, IJsonAssertionOptions<object>> config,
string because = "",
params object[] becauseArgs)
{
return BeEquivalentTo(subtree, true, config, because, becauseArgs);
}

#pragma warning disable CA1822 // Making this method static is a breaking chan
public string Format(JToken value, bool useLineBreaks = false)
{
Expand Down
29 changes: 29 additions & 0 deletions Tests/FluentAssertions.Json.Specs/JTokenAssertionsSpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -966,6 +966,35 @@ public void When_checking_subtree_with_an_invalid_expected_string_it_should_prov
.WithInnerException<JsonReaderException>();
}

[Fact]
public void When_a_float_is_within_approximation_ContainSubtree_check_should_succeed()
{
// Arrange
var actual = JToken.Parse("{ \"id\": 1.1232 }");
var expected = JToken.Parse("{ \"id\": 1.1235 }");

// Act & Assert
actual.Should().ContainSubtree(expected, options => options
.Using<double>(d => d.Subject.Should().BeApproximately(d.Expectation, 1e-3))
.WhenTypeIs<double>());
}

[Fact]
public void When_a_float_is_not_within_approximation_ContainSubtree_check_should_throw()
{
// Arrange
var actual = JToken.Parse("{ \"id\": 1.1232 }");
var expected = JToken.Parse("{ \"id\": 1.1235 }");

// Act & Assert
actual.Should().
Invoking(x => x.ContainSubtree(expected, options => options
.Using<double>(d => d.Subject.Should().BeApproximately(d.Expectation, 1e-5))
.WhenTypeIs<double>()))
.Should().Throw<XunitException>()
.WithMessage("JSON document has a different value at $.id.*");
}

#endregion

private static string Format(JToken value, bool useLineBreaks = false)
Expand Down
Loading