Skip to content

Commit 6958420

Browse files
ronaldkroondennisdoomen
authored andcommitted
Add BeValidJson for checking strings (#29)
* Add BeValidJson for checking strings It also allows consecutive checks on the parsed JSON using the Which property. The check does not limit the valid values to objects and arrays because of reasons explained here: https://stackoverflow.com/questions/7487869/is-this-simple-string-considered-valid-json
1 parent 28404d9 commit 6958420

File tree

6 files changed

+160
-20
lines changed

6 files changed

+160
-20
lines changed

FluentAssertions.Json.sln.DotSettings

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,5 @@ public void When_$scenario$_it_should_$behavior$()
9898
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=012E3B0572DEF2448B0B5D9AA88E6210/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
9999
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=012E3B0572DEF2448B0B5D9AA88E6210/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
100100
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=012E3B0572DEF2448B0B5D9AA88E6210/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
101-
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=012E3B0572DEF2448B0B5D9AA88E6210/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String></wpf:ResourceDictionary>
101+
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=012E3B0572DEF2448B0B5D9AA88E6210/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
102+
<s:Boolean x:Key="/Default/UserDictionary/Words/=jtoken/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

Src/FluentAssertions.Json/JTokenAssertions.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ public AndConstraint<JTokenAssertions> BeEquivalentTo(JToken expected, string be
7474
params object[] becauseArgs)
7575
{
7676
Difference difference = JTokenDifferentiator.FindFirstDifference(Subject, expected);
77-
JTokenFormatter formatter = new JTokenFormatter();
7877

7978
var message = $"Expected JSON document {Format(Subject, true).Replace("{", "{{").Replace("}", "}}")}" +
8079
$" to be equivalent to {Format(expected, true).Replace("{", "{{").Replace("}", "}}")}" +
@@ -209,7 +208,7 @@ public AndConstraint<JTokenAssertions> MatchRegex(string regularExpression, stri
209208
}
210209

211210
Execute.Assertion
212-
.ForCondition(Regex.IsMatch(Subject.Value<string>(), regularExpression ?? ""))
211+
.ForCondition(Regex.IsMatch(Subject.Value<string>(), regularExpression))
213212
.BecauseOf(because, becauseArgs)
214213
.FailWith("Expected {context:JSON property} {0} to match regex pattern {1}{reason}, but found {2}.",
215214
Subject.Path, regularExpression, Subject.Value<string>());
@@ -314,7 +313,6 @@ public AndWhichConstraint<JTokenAssertions, JToken> NotHaveElement(string unexpe
314313
/// </param>
315314
public AndWhichConstraint<JTokenAssertions, JToken> ContainSingleItem(string because = "", params object[] becauseArgs)
316315
{
317-
var formatter = new JTokenFormatter();
318316
string formattedDocument = Format(Subject).Replace("{", "{{").Replace("}", "}}");
319317

320318
using (new AssertionScope("JSON document " + formattedDocument))
@@ -337,7 +335,6 @@ public AndWhichConstraint<JTokenAssertions, JToken> ContainSingleItem(string bec
337335
/// </param>
338336
public AndConstraint<JTokenAssertions> HaveCount(int expected, string because = "", params object[] becauseArgs)
339337
{
340-
var formatter = new JTokenFormatter();
341338
string formattedDocument = Format(Subject).Replace("{", "{{").Replace("}", "}}");
342339

343340
using (new AssertionScope("JSON document " + formattedDocument))
@@ -452,7 +449,7 @@ private bool JTokenContainsSubtree(JToken token, JToken subtree)
452449

453450
}
454451
}
455-
452+
456453
public string Format(JToken value, bool useLineBreaks = false)
457454
{
458455
return new JTokenFormatter().Format(value, new FormattingContext
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System;
2+
using FluentAssertions.Execution;
3+
using FluentAssertions.Primitives;
4+
using Newtonsoft.Json.Linq;
5+
6+
namespace FluentAssertions.Json
7+
{
8+
public static class StringAssertionsExtensions
9+
{
10+
[CustomAssertionAttribute]
11+
public static AndWhichConstraint<StringAssertions, JToken> BeValidJson(
12+
this StringAssertions stringAssertions,
13+
string because = "",
14+
params object[] becauseArgs)
15+
{
16+
JToken json = null;
17+
18+
try
19+
{
20+
json = JToken.Parse(stringAssertions.Subject);
21+
}
22+
catch (Exception ex)
23+
{
24+
Execute.Assertion.BecauseOf(because, becauseArgs)
25+
.FailWith("Expected {context:string} to be valid JSON{reason}, but parsing failed with {0}.", ex.Message);
26+
}
27+
28+
return new AndWhichConstraint<StringAssertions, JToken>(stringAssertions, json);
29+
}
30+
}
31+
}

Tests/FluentAssertions.Json.Shared.Specs/JTokenAssertionsSpecs.cs

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,9 @@
88
namespace FluentAssertions.Json
99
{
1010
// ReSharper disable InconsistentNaming
11+
// ReSharper disable ExpressionIsAlwaysNull
1112
public class JTokenAssertionsSpecs
1213
{
13-
14-
private static readonly JTokenFormatter _formatter = new JTokenFormatter();
15-
1614
#region (Not)BeEquivalentTo
1715

1816
[Fact]
@@ -642,7 +640,7 @@ public void When_jtoken_is_null_ContainSingleItem_should_fail()
642640
// Assert
643641
//-----------------------------------------------------------------------------------------------------------
644642
act.Should().Throw<XunitException>()
645-
.WithMessage($"Expected JSON document <null> to contain a single item because null is not allowed, but found <null>.");
643+
.WithMessage("Expected JSON document <null> to contain a single item because null is not allowed, but found <null>.");
646644
}
647645

648646
[Fact]
@@ -662,7 +660,7 @@ public void When_jtoken_is_an_empty_object_ContainSingleItem_should_fail()
662660
// Assert
663661
//-----------------------------------------------------------------------------------------------------------
664662
act.Should().Throw<XunitException>()
665-
.WithMessage($"Expected JSON document * to contain a single item because less is not allowed, but the collection is empty.");
663+
.WithMessage("Expected JSON document * to contain a single item because less is not allowed, but the collection is empty.");
666664
}
667665

668666
[Fact]
@@ -681,10 +679,8 @@ public void When_jtoken_has_multiple_elements_ContainSingleItem_should_fail()
681679
//-----------------------------------------------------------------------------------------------------------
682680
// Assert
683681
//-----------------------------------------------------------------------------------------------------------
684-
string formattedSubject = Format(subject);
685-
686682
act.Should().Throw<XunitException>()
687-
.WithMessage($"Expected JSON document*id*42*admin*true*to contain a single item because more is not allowed, but found*");
683+
.WithMessage("Expected JSON document*id*42*admin*true*to contain a single item because more is not allowed, but found*");
688684
}
689685

690686
[Fact]
@@ -742,7 +738,7 @@ public void When_jtoken_is_an_empty_array_ContainSingleItem_should_fail()
742738
// Assert
743739
//-----------------------------------------------------------------------------------------------------------
744740
act.Should().Throw<XunitException>()
745-
.WithMessage($"Expected JSON document [] to contain a single item because less is not allowed, but the collection is empty.");
741+
.WithMessage("Expected JSON document [] to contain a single item because less is not allowed, but the collection is empty.");
746742
}
747743

748744
[Fact]
@@ -826,7 +822,7 @@ public void When_jtoken_is_null_HaveCount_should_fail()
826822
// Assert
827823
//-----------------------------------------------------------------------------------------------------------
828824
act.Should().Throw<XunitException>()
829-
.WithMessage($"Expected JSON document <null> to contain 1 item(s) because null is not allowed, but found <null>.");
825+
.WithMessage("Expected JSON document <null> to contain 1 item(s) because null is not allowed, but found <null>.");
830826
}
831827

832828
[Fact]
@@ -846,7 +842,7 @@ public void When_expecting_a_different_number_of_elements_than_the_actual_number
846842
// Assert
847843
//-----------------------------------------------------------------------------------------------------------
848844
act.Should().Throw<XunitException>()
849-
.WithMessage($"Expected JSON document * to contain 1 item(s) because numbers matter, but found 0.");
845+
.WithMessage("Expected JSON document * to contain 1 item(s) because numbers matter, but found 0.");
850846
}
851847

852848
[Fact]
@@ -885,7 +881,7 @@ public void When_expecting_a_different_number_of_array_items_than_the_actual_num
885881
// Assert
886882
//-----------------------------------------------------------------------------------------------------------
887883
act.Should().Throw<XunitException>()
888-
.WithMessage($"Expected JSON document * to contain 3 item(s) because the more the better, but found 2.");
884+
.WithMessage("Expected JSON document * to contain 3 item(s) because the more the better, but found 2.");
889885
}
890886

891887
#endregion HaveCount
@@ -1026,13 +1022,13 @@ public void When_property_types_dont_match_ContainSubtree_should_fail()
10261022
}
10271023

10281024
#endregion
1029-
public static string Format(JToken value, bool useLineBreaks = false)
1025+
1026+
private static string Format(JToken value, bool useLineBreaks = false)
10301027
{
10311028
return new JTokenFormatter().Format(value, new FormattingContext
10321029
{
10331030
UseLineBreaks = useLineBreaks
10341031
}, null);
10351032
}
1036-
10371033
}
10381034
}

Tests/FluentAssertions.Json.Shared.Specs/Shared.Json.Specs.projitems

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<Compile Include="$(MSBuildThisFileDirectory)JsonAssertionExtensionsSpecs.cs" />
1313
<Compile Include="$(MSBuildThisFileDirectory)JTokenAssertionsSpecs.cs" />
1414
<Compile Include="$(MSBuildThisFileDirectory)JTokenFormatterSpecs.cs" />
15+
<Compile Include="$(MSBuildThisFileDirectory)StringAssertionsExtensionsSpecs.cs" />
1516
<Compile Include="$(MSBuildThisFileDirectory)StringExtensions.cs" />
1617
</ItemGroup>
1718
</Project>
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
using System;
2+
using Newtonsoft.Json.Linq;
3+
using Xunit;
4+
using Xunit.Sdk;
5+
6+
namespace FluentAssertions.Json.Net45.Specs
7+
{
8+
// ReSharper disable ExpressionIsAlwaysNull
9+
public class StringAssertionsExtensionsSpecs
10+
{
11+
#region BeValidJson
12+
13+
[Fact]
14+
public void When_checking_valid_json_BeValidJson_should_succeed()
15+
{
16+
//-----------------------------------------------------------------------------------------------------------
17+
// Arrange
18+
//-----------------------------------------------------------------------------------------------------------
19+
string subject = "{ id: 42, admin: true }";
20+
21+
//-----------------------------------------------------------------------------------------------------------
22+
// Act
23+
//-----------------------------------------------------------------------------------------------------------
24+
Action act = () => subject.Should().BeValidJson();
25+
26+
//-----------------------------------------------------------------------------------------------------------
27+
// Assert
28+
//-----------------------------------------------------------------------------------------------------------
29+
act.Should().NotThrow();
30+
}
31+
32+
[Fact]
33+
public void When_checking_valid_json_BeValidJson_should_enable_consecutive_jtoken_assertions()
34+
{
35+
//-----------------------------------------------------------------------------------------------------------
36+
// Arrange
37+
//-----------------------------------------------------------------------------------------------------------
38+
string subject = "{ id: 42 }";
39+
40+
//-----------------------------------------------------------------------------------------------------------
41+
// Act
42+
//-----------------------------------------------------------------------------------------------------------
43+
object which = subject.Should().BeValidJson().Which;
44+
45+
//-----------------------------------------------------------------------------------------------------------
46+
// Assert
47+
//-----------------------------------------------------------------------------------------------------------
48+
which.Should().BeAssignableTo<JToken>();
49+
}
50+
51+
[Fact]
52+
public void When_checking_null_BeValidJson_should_fail()
53+
{
54+
//-----------------------------------------------------------------------------------------------------------
55+
// Arrange
56+
//-----------------------------------------------------------------------------------------------------------
57+
string subject = null;
58+
Exception caughtException = null;
59+
60+
//-----------------------------------------------------------------------------------------------------------
61+
// Act
62+
//-----------------------------------------------------------------------------------------------------------
63+
try
64+
{
65+
subject.Should().BeValidJson("null is not allowed");
66+
}
67+
catch (Exception ex)
68+
{
69+
caughtException = ex;
70+
}
71+
72+
//-----------------------------------------------------------------------------------------------------------
73+
// Assert
74+
//-----------------------------------------------------------------------------------------------------------
75+
caughtException.Should()
76+
.BeOfType<XunitException>()
77+
.Which.Message.Should()
78+
.Match("Expected subject to be valid JSON because null is not allowed, but parsing failed with \"*\".");
79+
}
80+
81+
[Fact]
82+
public void When_checking_invalid_json_BeValidJson_should_fail()
83+
{
84+
//-----------------------------------------------------------------------------------------------------------
85+
// Arrange
86+
//-----------------------------------------------------------------------------------------------------------
87+
string subject = "invalid json";
88+
Exception caughtException = null;
89+
90+
//-----------------------------------------------------------------------------------------------------------
91+
// Act
92+
//-----------------------------------------------------------------------------------------------------------
93+
try
94+
{
95+
subject.Should().BeValidJson("we like {0}", "JSON");
96+
}
97+
catch (Exception ex)
98+
{
99+
caughtException = ex;
100+
}
101+
102+
//-----------------------------------------------------------------------------------------------------------
103+
// Assert
104+
//-----------------------------------------------------------------------------------------------------------
105+
caughtException.Should()
106+
.BeOfType<XunitException>()
107+
.Which.Message.Should()
108+
.Match("Expected subject to be valid JSON because we like JSON, but parsing failed with \"*\".");
109+
}
110+
111+
#endregion
112+
113+
}
114+
}

0 commit comments

Comments
 (0)