From 7203f29a3480defe87a0cde8a41900fb911e6705 Mon Sep 17 00:00:00 2001 From: Sam Segers Date: Sun, 4 May 2025 10:52:47 +0200 Subject: [PATCH 1/4] add ContainSubtree that takes a config --- Src/FluentAssertions.Json/JTokenAssertions.cs | 37 ++++++++++++++++++- .../JTokenAssertionsSpecs.cs | 29 +++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/Src/FluentAssertions.Json/JTokenAssertions.cs b/Src/FluentAssertions.Json/JTokenAssertions.cs index 19f8215..2cc65e0 100644 --- a/Src/FluentAssertions.Json/JTokenAssertions.cs +++ b/Src/FluentAssertions.Json/JTokenAssertions.cs @@ -27,7 +27,7 @@ static JTokenAssertions() /// Initializes a new instance of the class. /// /// The subject - /// + /// The assertion chain public JTokenAssertions(JToken subject, AssertionChain assertionChain) : base(subject, assertionChain) { @@ -496,6 +496,41 @@ public AndConstraint ContainSubtree(JToken subtree, string bec return BeEquivalentTo(subtree, true, options => options, because, becauseArgs); } + /// + /// Recursively asserts that the current contains at least the properties or elements of the specified . + /// + /// The subtree to search for + /// The options to consider while asserting values + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + /// Use this method to match the current against an arbitrary subtree, + /// permitting it to contain any additional properties or elements. This way we can test multiple properties on a at once, + /// or test if a contains any items that match a set of properties, assert that a JSON document has a given shape, etc. + /// + /// This example asserts the values of multiple properties of a child object within a JSON document. + /// + /// 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' } }")); + /// + /// + /// This example asserts that a within a has at least one element with at least the given properties + /// + /// 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' } ] }")); + /// + public AndConstraint ContainSubtree(JToken subtree, + Func, IJsonAssertionOptions> 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) { diff --git a/Tests/FluentAssertions.Json.Specs/JTokenAssertionsSpecs.cs b/Tests/FluentAssertions.Json.Specs/JTokenAssertionsSpecs.cs index 6193dcd..eae18b2 100644 --- a/Tests/FluentAssertions.Json.Specs/JTokenAssertionsSpecs.cs +++ b/Tests/FluentAssertions.Json.Specs/JTokenAssertionsSpecs.cs @@ -966,6 +966,35 @@ public void When_checking_subtree_with_an_invalid_expected_string_it_should_prov .WithInnerException(); } + [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(d => d.Subject.Should().BeApproximately(d.Expectation, 1e-3)) + .WhenTypeIs()); + } + + [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(d => d.Subject.Should().BeApproximately(d.Expectation, 1e-5)) + .WhenTypeIs())) + .Should().Throw() + .WithMessage("JSON document has a different value at $.id.*"); + } + #endregion private static string Format(JToken value, bool useLineBreaks = false) From 60f2e9d4bbbf564f8ee462bf255cab081856285d Mon Sep 17 00:00:00 2001 From: Sam Segers Date: Tue, 30 Sep 2025 08:27:36 +0200 Subject: [PATCH 2/4] Fix example documentation in JTokenAssertions --- Src/FluentAssertions.Json/JTokenAssertions.cs | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/Src/FluentAssertions.Json/JTokenAssertions.cs b/Src/FluentAssertions.Json/JTokenAssertions.cs index 2cc65e0..0331a5d 100644 --- a/Src/FluentAssertions.Json/JTokenAssertions.cs +++ b/Src/FluentAssertions.Json/JTokenAssertions.cs @@ -437,15 +437,17 @@ public AndConstraint HaveCount(int expected, string because = /// /// This example asserts the values of multiple properties of a child object within a JSON document. /// - /// 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' } }")); + /// var json = JToken.Parse("{ success: true, data: { id: 123, type: 'my-type', name: 'None' } }"); + /// json.Should().ContainSubtree(JToken.Parse("{ success: true, data: { type: 'my-type', name: 'None' } }")); /// /// - /// This example asserts that a within a has at least one element with at least the given properties + /// + /// This example asserts that a within a has at least one element with at least the given properties /// /// 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' } ] }")); /// + /// public AndConstraint ContainSubtree(string subtree, string because = "", params object[] becauseArgs) { JToken subtreeToken; @@ -482,15 +484,17 @@ public AndConstraint ContainSubtree(string subtree, string bec /// /// This example asserts the values of multiple properties of a child object within a JSON document. /// - /// 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' } }")); + /// var json = JToken.Parse("{ success: true, data: { id: 123, type: 'my-type', name: 'None' } }"); + /// json.Should().ContainSubtree(JToken.Parse("{ success: true, data: { type: 'my-type', name: 'None' } }")); /// /// - /// This example asserts that a within a has at least one element with at least the given properties + /// + /// This example asserts that a within a has at least one element with at least the given properties /// /// 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' } ] }")); /// + /// public AndConstraint ContainSubtree(JToken subtree, string because = "", params object[] becauseArgs) { return BeEquivalentTo(subtree, true, options => options, because, becauseArgs); @@ -511,18 +515,6 @@ public AndConstraint ContainSubtree(JToken subtree, string bec /// Use this method to match the current against an arbitrary subtree, /// permitting it to contain any additional properties or elements. This way we can test multiple properties on a at once, /// or test if a contains any items that match a set of properties, assert that a JSON document has a given shape, etc. - /// - /// This example asserts the values of multiple properties of a child object within a JSON document. - /// - /// 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' } }")); - /// - /// - /// This example asserts that a within a has at least one element with at least the given properties - /// - /// 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' } ] }")); - /// public AndConstraint ContainSubtree(JToken subtree, Func, IJsonAssertionOptions> config, string because = "", From fc15ec756eeb74ac4a43fd65dae2884e780e4500 Mon Sep 17 00:00:00 2001 From: Sam Segers Date: Tue, 30 Sep 2025 08:49:13 +0200 Subject: [PATCH 3/4] Update tests to match new guidelines --- .../JTokenAssertionsSpecs.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Tests/FluentAssertions.Json.Specs/JTokenAssertionsSpecs.cs b/Tests/FluentAssertions.Json.Specs/JTokenAssertionsSpecs.cs index eae18b2..4af0420 100644 --- a/Tests/FluentAssertions.Json.Specs/JTokenAssertionsSpecs.cs +++ b/Tests/FluentAssertions.Json.Specs/JTokenAssertionsSpecs.cs @@ -967,7 +967,7 @@ public void When_checking_subtree_with_an_invalid_expected_string_it_should_prov } [Fact] - public void When_a_float_is_within_approximation_ContainSubtree_check_should_succeed() + public void Assert_property_with_approximation_succeeds() { // Arrange var actual = JToken.Parse("{ \"id\": 1.1232 }"); @@ -980,18 +980,19 @@ public void When_a_float_is_within_approximation_ContainSubtree_check_should_suc } [Fact] - public void When_a_float_is_not_within_approximation_ContainSubtree_check_should_throw() + public void Can_assert_on_a_field_with_approximation() { // 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(d => d.Subject.Should().BeApproximately(d.Expectation, 1e-5)) - .WhenTypeIs())) - .Should().Throw() + // Act + Action act = () => actual.Should().ContainSubtree(expected, options => options + .Using(d => d.Subject.Should().BeApproximately(d.Expectation, 1e-5)) + .WhenTypeIs()); + + // Assert + act.Should().Throw() .WithMessage("JSON document has a different value at $.id.*"); } From df424eac47ba6391ab823e87132dcfa82c7a4252 Mon Sep 17 00:00:00 2001 From: Sam Segers Date: Tue, 30 Sep 2025 08:50:29 +0200 Subject: [PATCH 4/4] Update ApprovedApi --- .../FluentAssertions.Json/net47.verified.txt | 73 ------------------- .../netstandard2.0.verified.txt | 73 ------------------- 2 files changed, 146 deletions(-) delete mode 100644 Tests/Approval.Tests/ApprovedApi/FluentAssertions.Json/net47.verified.txt delete mode 100644 Tests/Approval.Tests/ApprovedApi/FluentAssertions.Json/netstandard2.0.verified.txt diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions.Json/net47.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions.Json/net47.verified.txt deleted file mode 100644 index 74b3514..0000000 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions.Json/net47.verified.txt +++ /dev/null @@ -1,73 +0,0 @@ -[assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/fluentassertions/fluentassertions.json.git")] -namespace FluentAssertions.Json -{ - public interface IJsonAssertionOptions - { - FluentAssertions.Json.IJsonAssertionRestriction Using(System.Action> action); - } - public interface IJsonAssertionRestriction - { - FluentAssertions.Json.IJsonAssertionOptions WhenTypeIs() - where TMemberType : TMember; - } - public class JTokenAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions - { - public JTokenAssertions(Newtonsoft.Json.Linq.JToken subject, FluentAssertions.Execution.AssertionChain assertionChain) { } - protected override string Identifier { get; } - public FluentAssertions.AndConstraint BeEquivalentTo(Newtonsoft.Json.Linq.JToken expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeEquivalentTo(string expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeEquivalentTo(Newtonsoft.Json.Linq.JToken expected, System.Func, FluentAssertions.Json.IJsonAssertionOptions> config, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint ContainSingleItem(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint ContainSubtree(Newtonsoft.Json.Linq.JToken subtree, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint ContainSubtree(string subtree, string because = "", params object[] becauseArgs) { } - public string Format(Newtonsoft.Json.Linq.JToken value, bool useLineBreaks = false) { } - public FluentAssertions.AndConstraint HaveCount(int expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint HaveElement(string expected) { } - public FluentAssertions.AndWhichConstraint HaveElement(string expected, string because, params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveValue(string expected) { } - public FluentAssertions.AndConstraint HaveValue(string expected, string because, params object[] becauseArgs) { } - public FluentAssertions.AndConstraint MatchRegex(string regularExpression) { } - public FluentAssertions.AndConstraint MatchRegex(string regularExpression, string because, params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotBeEquivalentTo(Newtonsoft.Json.Linq.JToken unexpected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotBeEquivalentTo(string unexpected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint NotHaveElement(string unexpected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotHaveValue(string unexpected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotMatchRegex(string regularExpression, string because = "", params object[] becauseArgs) { } - } - public class JTokenFormatter : FluentAssertions.Formatting.IValueFormatter - { - public JTokenFormatter() { } - public bool CanHandle(object value) { } - public void Format(object value, FluentAssertions.Formatting.FormattedObjectGraph formattedGraph, FluentAssertions.Formatting.FormattingContext context, FluentAssertions.Formatting.FormatChild formatChild) { } - } - public static class JsonAssertionExtensions - { - public static FluentAssertions.Json.JTokenAssertions Should(this Newtonsoft.Json.Linq.JObject jObject) { } - public static FluentAssertions.Json.JTokenAssertions Should(this Newtonsoft.Json.Linq.JToken jToken) { } - public static FluentAssertions.Json.JTokenAssertions Should(this Newtonsoft.Json.Linq.JValue jValue) { } - } - public sealed class JsonAssertionOptions : FluentAssertions.Equivalency.EquivalencyOptions, FluentAssertions.Json.IJsonAssertionOptions - { - public JsonAssertionOptions(FluentAssertions.Equivalency.EquivalencyOptions equivalencyAssertionOptions) { } - public FluentAssertions.Json.IJsonAssertionRestriction Using(System.Action> action) { } - } - public sealed class JsonAssertionRestriction : FluentAssertions.Json.IJsonAssertionRestriction - { - public FluentAssertions.Json.IJsonAssertionOptions WhenTypeIs() - where TMemberType : TProperty { } - } - public static class ObjectAssertionsExtensions - { - [FluentAssertions.CustomAssertion] - public static FluentAssertions.AndConstraint BeJsonSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, string because = "", params object[] becauseArgs) { } - [FluentAssertions.CustomAssertion] - public static FluentAssertions.AndConstraint BeJsonSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, string because = "", params object[] becauseArgs) { } - [FluentAssertions.CustomAssertion] - public static FluentAssertions.AndConstraint BeJsonSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> options, string because = "", params object[] becauseArgs) { } - } - public static class StringAssertionsExtensions - { - [FluentAssertions.CustomAssertion] - public static FluentAssertions.AndWhichConstraint BeValidJson(this FluentAssertions.Primitives.StringAssertions stringAssertions, string because = "", params object[] becauseArgs) { } - } -} \ No newline at end of file diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions.Json/netstandard2.0.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions.Json/netstandard2.0.verified.txt deleted file mode 100644 index 74b3514..0000000 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions.Json/netstandard2.0.verified.txt +++ /dev/null @@ -1,73 +0,0 @@ -[assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/fluentassertions/fluentassertions.json.git")] -namespace FluentAssertions.Json -{ - public interface IJsonAssertionOptions - { - FluentAssertions.Json.IJsonAssertionRestriction Using(System.Action> action); - } - public interface IJsonAssertionRestriction - { - FluentAssertions.Json.IJsonAssertionOptions WhenTypeIs() - where TMemberType : TMember; - } - public class JTokenAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions - { - public JTokenAssertions(Newtonsoft.Json.Linq.JToken subject, FluentAssertions.Execution.AssertionChain assertionChain) { } - protected override string Identifier { get; } - public FluentAssertions.AndConstraint BeEquivalentTo(Newtonsoft.Json.Linq.JToken expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeEquivalentTo(string expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeEquivalentTo(Newtonsoft.Json.Linq.JToken expected, System.Func, FluentAssertions.Json.IJsonAssertionOptions> config, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint ContainSingleItem(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint ContainSubtree(Newtonsoft.Json.Linq.JToken subtree, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint ContainSubtree(string subtree, string because = "", params object[] becauseArgs) { } - public string Format(Newtonsoft.Json.Linq.JToken value, bool useLineBreaks = false) { } - public FluentAssertions.AndConstraint HaveCount(int expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint HaveElement(string expected) { } - public FluentAssertions.AndWhichConstraint HaveElement(string expected, string because, params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveValue(string expected) { } - public FluentAssertions.AndConstraint HaveValue(string expected, string because, params object[] becauseArgs) { } - public FluentAssertions.AndConstraint MatchRegex(string regularExpression) { } - public FluentAssertions.AndConstraint MatchRegex(string regularExpression, string because, params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotBeEquivalentTo(Newtonsoft.Json.Linq.JToken unexpected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotBeEquivalentTo(string unexpected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint NotHaveElement(string unexpected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotHaveValue(string unexpected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotMatchRegex(string regularExpression, string because = "", params object[] becauseArgs) { } - } - public class JTokenFormatter : FluentAssertions.Formatting.IValueFormatter - { - public JTokenFormatter() { } - public bool CanHandle(object value) { } - public void Format(object value, FluentAssertions.Formatting.FormattedObjectGraph formattedGraph, FluentAssertions.Formatting.FormattingContext context, FluentAssertions.Formatting.FormatChild formatChild) { } - } - public static class JsonAssertionExtensions - { - public static FluentAssertions.Json.JTokenAssertions Should(this Newtonsoft.Json.Linq.JObject jObject) { } - public static FluentAssertions.Json.JTokenAssertions Should(this Newtonsoft.Json.Linq.JToken jToken) { } - public static FluentAssertions.Json.JTokenAssertions Should(this Newtonsoft.Json.Linq.JValue jValue) { } - } - public sealed class JsonAssertionOptions : FluentAssertions.Equivalency.EquivalencyOptions, FluentAssertions.Json.IJsonAssertionOptions - { - public JsonAssertionOptions(FluentAssertions.Equivalency.EquivalencyOptions equivalencyAssertionOptions) { } - public FluentAssertions.Json.IJsonAssertionRestriction Using(System.Action> action) { } - } - public sealed class JsonAssertionRestriction : FluentAssertions.Json.IJsonAssertionRestriction - { - public FluentAssertions.Json.IJsonAssertionOptions WhenTypeIs() - where TMemberType : TProperty { } - } - public static class ObjectAssertionsExtensions - { - [FluentAssertions.CustomAssertion] - public static FluentAssertions.AndConstraint BeJsonSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, string because = "", params object[] becauseArgs) { } - [FluentAssertions.CustomAssertion] - public static FluentAssertions.AndConstraint BeJsonSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, string because = "", params object[] becauseArgs) { } - [FluentAssertions.CustomAssertion] - public static FluentAssertions.AndConstraint BeJsonSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> options, string because = "", params object[] becauseArgs) { } - } - public static class StringAssertionsExtensions - { - [FluentAssertions.CustomAssertion] - public static FluentAssertions.AndWhichConstraint BeValidJson(this FluentAssertions.Primitives.StringAssertions stringAssertions, string because = "", params object[] becauseArgs) { } - } -} \ No newline at end of file