Skip to content

Commit 0619a4b

Browse files
authored
Merge pull request #5 from ronaldkroon/Add_a_few_JToken_assertions
Add a few JToken assertions
2 parents 4e8a76f + 39a4049 commit 0619a4b

File tree

3 files changed

+567
-72
lines changed

3 files changed

+567
-72
lines changed

Src/FluentAssertions.Json.Shared/JTokenAssertions.cs

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Diagnostics;
2+
using FluentAssertions.Collections;
23
using FluentAssertions.Common;
34
using FluentAssertions.Execution;
45
using 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
}

Src/FluentAssertions.Json.Shared/JTokenFormatter.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ public string ToString(object value, bool useLineBreaks = false, IList<object> p
4141
int nestedPropertyLevel = 0)
4242
{
4343
var jToken = (JToken)value;
44-
return useLineBreaks ? jToken.ToString(Newtonsoft.Json.Formatting.Indented) : jToken.ToString().RemoveNewLines();
44+
string result = useLineBreaks ? jToken?.ToString(Newtonsoft.Json.Formatting.Indented) : jToken?.ToString().RemoveNewLines();
45+
return result ?? "<null>";
4546
}
4647
}
4748
}

0 commit comments

Comments
 (0)