11using System . Diagnostics ;
2+ using FluentAssertions . Collections ;
23using FluentAssertions . Common ;
34using FluentAssertions . Execution ;
45using FluentAssertions . Formatting ;
@@ -13,6 +14,8 @@ namespace FluentAssertions.Json
1314 [ DebuggerNonUserCode ]
1415 public class JTokenAssertions : ReferenceTypeAssertions < JToken , JTokenAssertions >
1516 {
17+ private GenericCollectionAssertions < JToken > EnumerableSubject { get ; }
18+
1619 static JTokenAssertions ( )
1720 {
1821 Formatter . AddFormatter ( new JTokenFormatter ( ) ) ;
@@ -25,6 +28,7 @@ static JTokenAssertions()
2528 public JTokenAssertions ( JToken subject )
2629 {
2730 Subject = subject ;
31+ EnumerableSubject = new GenericCollectionAssertions < JToken > ( subject ) ;
2832 }
2933
3034 /// <summary>
@@ -59,7 +63,7 @@ public AndConstraint<JTokenAssertions> Be(JToken expected, string because, param
5963 {
6064 Execute . Assertion
6165 . ForCondition ( JToken . DeepEquals ( Subject , expected ) )
62- . BecauseOf ( because , becauseArgs )
66+ . BecauseOf ( because , becauseArgs )
6367 . FailWith ( "Expected JSON document to be {0}{reason}, but found {1}." , expected , Subject ) ;
6468
6569 return new AndConstraint < JTokenAssertions > ( this ) ;
@@ -105,7 +109,7 @@ public AndConstraint<JTokenAssertions> NotBe(JToken unexpected, string because,
105109 /// <param name="expected">The expected element</param>
106110 /// <remarks>
107111 /// Json tokens are compared by inspecting all properties. They are considered equal
108- /// when all property names and values match (independent of their order).
112+ /// when all property names and values match (independent of their order).
109113 /// When not equal, the first mismatching property or value is included in the assertion failure message.
110114 /// </remarks>
111115 public AndConstraint < JTokenAssertions > BeEquivalentTo ( JToken expected )
@@ -134,7 +138,7 @@ public AndConstraint<JTokenAssertions> BeEquivalentTo(JToken expected, string be
134138
135139 Execute . Assertion
136140 . ForCondition ( diff . AreEqual )
137- . BecauseOf ( because , becauseArgs )
141+ . BecauseOf ( because , becauseArgs )
138142 . FailWith ( $ "Expected JSON document { formatter . ToString ( Subject , true ) } " +
139143 $ " to be equivalent to { formatter . ToString ( expected , true ) } " +
140144 $ "{{reason}}, but differs at { firstDifferingToken } .") ;
@@ -207,6 +211,28 @@ public AndConstraint<JTokenAssertions> HaveValue(string expected, string because
207211 return new AndConstraint < JTokenAssertions > ( this ) ;
208212 }
209213
214+ /// <summary>
215+ /// Asserts that the current <see cref="JToken" /> does not have the specified <paramref name="unexpected" /> value.
216+ /// </summary>
217+ /// <param name="unexpected">The unexpected element</param>
218+ /// <param name="because">
219+ /// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
220+ /// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
221+ /// </param>
222+ /// <param name="becauseArgs">
223+ /// Zero or more objects to format using the placeholders in <see paramref="because" />.
224+ /// </param>
225+ public AndConstraint < JTokenAssertions > NotHaveValue ( string unexpected , string because = "" , params object [ ] becauseArgs )
226+ {
227+ Execute . Assertion
228+ . ForCondition ( Subject . Value < string > ( ) != unexpected )
229+ . BecauseOf ( because , becauseArgs )
230+ . FailWith ( "Did not expect JSON property {0} to have value {1}{reason}." ,
231+ Subject . Path , unexpected , Subject . Value < string > ( ) ) ;
232+
233+ return new AndConstraint < JTokenAssertions > ( this ) ;
234+ }
235+
210236 /// <summary>
211237 /// Asserts that the current <see cref="JToken" /> has a direct child element with the specified
212238 /// <paramref name="expected" /> name.
@@ -241,5 +267,74 @@ public AndWhichConstraint<JTokenAssertions, JToken> HaveElement(string expected,
241267
242268 return new AndWhichConstraint < JTokenAssertions , JToken > ( this , jToken ) ;
243269 }
270+
271+ /// <summary>
272+ /// Asserts that the current <see cref="JToken" /> does not have a direct child element with the specified
273+ /// <paramref name="unexpected" /> name.
274+ /// </summary>
275+ /// <param name="unexpected">The name of the not expected child element</param>
276+ /// <param name="because">
277+ /// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
278+ /// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
279+ /// </param>
280+ /// <param name="becauseArgs">
281+ /// Zero or more objects to format using the placeholders in <see paramref="because" />.
282+ /// </param>
283+ public AndWhichConstraint < JTokenAssertions , JToken > NotHaveElement ( string unexpected , string because = "" ,
284+ params object [ ] becauseArgs )
285+ {
286+ JToken jToken = Subject [ unexpected ] ;
287+ Execute . Assertion
288+ . ForCondition ( jToken == null )
289+ . BecauseOf ( because , becauseArgs )
290+ . FailWith ( "Did not expect JSON document {0} to have element \" " + unexpected . Escape ( true ) + "\" {reason}." , Subject ) ;
291+
292+ return new AndWhichConstraint < JTokenAssertions , JToken > ( this , jToken ) ;
293+ }
294+
295+ /// <summary>
296+ /// Expects the current <see cref="JToken" /> to contain only a single item.
297+ /// </summary>
298+ /// <param name="because">
299+ /// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
300+ /// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
301+ /// </param>
302+ /// <param name="becauseArgs">
303+ /// Zero or more objects to format using the placeholders in <see cref="because" />.
304+ /// </param>
305+ public AndWhichConstraint < JTokenAssertions , JToken > ContainSingleItem ( string because = "" , params object [ ] becauseArgs )
306+ {
307+ var formatter = new JTokenFormatter ( ) ;
308+ string formattedDocument = formatter . ToString ( Subject ) . Replace ( "{" , "{{" ) . Replace ( "}" , "}}" ) ;
309+
310+ using ( new AssertionScope ( "JSON document " + formattedDocument ) )
311+ {
312+ var constraint = EnumerableSubject . ContainSingle ( because , becauseArgs ) ;
313+ return new AndWhichConstraint < JTokenAssertions , JToken > ( this , constraint . Which ) ;
314+ }
315+ }
316+
317+ /// <summary>
318+ /// Asserts that the number of items in the current <see cref="JToken" /> matches the supplied <paramref name="expected" /> amount.
319+ /// </summary>
320+ /// <param name="expected">The expected number of items in the current <see cref="JToken" />.</param>
321+ /// <param name="because">
322+ /// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
323+ /// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
324+ /// </param>
325+ /// <param name="becauseArgs">
326+ /// Zero or more objects to format using the placeholders in <see cref="because" />.
327+ /// </param>
328+ public AndConstraint < JTokenAssertions > HaveCount ( int expected , string because = "" , params object [ ] becauseArgs )
329+ {
330+ var formatter = new JTokenFormatter ( ) ;
331+ string formattedDocument = formatter . ToString ( Subject ) . Replace ( "{" , "{{" ) . Replace ( "}" , "}}" ) ;
332+
333+ using ( new AssertionScope ( "JSON document " + formattedDocument ) )
334+ {
335+ EnumerableSubject . HaveCount ( expected , because , becauseArgs ) ;
336+ return new AndConstraint < JTokenAssertions > ( this ) ;
337+ }
338+ }
244339 }
245340}
0 commit comments