diff --git a/Src/FluentAssertions.Json/JTokenAssertions.cs b/Src/FluentAssertions.Json/JTokenAssertions.cs
index 19f8215..0331a5d 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)
{
@@ -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,20 +484,45 @@ 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);
}
+ ///
+ /// 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.
+ 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/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
diff --git a/Tests/FluentAssertions.Json.Specs/JTokenAssertionsSpecs.cs b/Tests/FluentAssertions.Json.Specs/JTokenAssertionsSpecs.cs
index 6193dcd..4af0420 100644
--- a/Tests/FluentAssertions.Json.Specs/JTokenAssertionsSpecs.cs
+++ b/Tests/FluentAssertions.Json.Specs/JTokenAssertionsSpecs.cs
@@ -966,6 +966,36 @@ public void When_checking_subtree_with_an_invalid_expected_string_it_should_prov
.WithInnerException();
}
+ [Fact]
+ public void Assert_property_with_approximation_succeeds()
+ {
+ // 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 Can_assert_on_a_field_with_approximation()
+ {
+ // Arrange
+ var actual = JToken.Parse("{ \"id\": 1.1232 }");
+ var expected = JToken.Parse("{ \"id\": 1.1235 }");
+
+ // 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.*");
+ }
+
#endregion
private static string Format(JToken value, bool useLineBreaks = false)