Skip to main content

aweXpect.Json

Nuget

aweXpect.Json contains expectations for JSON strings, JsonElement and the JSON serialization of objects with System.Text.Json.

Overview​

ExpectationSubjectSummary
IsEqualTo(…).AsJson()stringequal to the expected JSON, compared by structure
IsValidJsonstringcan be parsed as JSON
IsValidJsonMatchingstringvalid JSON that matches the expected value
IsValidJsonMatchingExactlystringvalid JSON that matches the expected value exactly
MatchesJsonElementmatches the expected value, additional properties allowed
MatchesExactlyJsonElementmatches the expected value exactly
IsObjectJsonElementa JSON object, optionally with expectations on it
IsArrayJsonElementa JSON array, optionally with expectations on it
IsJsonSerializableobjectsurvives a round trip through the JSON serializer

String comparison as JSON​

You can verify that a string is equal to the expected JSON, compared by its structure instead of its formatting:

string track = """{"title":"Let It Be","artist":"The Beatles","durationSeconds":243}""";
string expected = """
{
"title": "Let It Be",
"artist": "The Beatles",
"durationSeconds": 243
}
""";

await Expect.That(track).IsEqualTo(expected).AsJson();

If the track had a duration of 241 seconds instead, the expectation would fail with:

Failure message
Expected that track
is JSON equivalent to {
"title": "Let It Be",
"artist": "The Beatles",
"durationSeconds": 243
},
but it differed as $.durationSeconds was 241 instead of 243

Actual:
{"title":"Let It Be","artist":"The Beatles","durationSeconds":241}

Expected:
{
"title": "Let It Be",
"artist": "The Beatles",
"durationSeconds": 243
}

The expected string must be valid JSON, otherwise an ArgumentException is thrown. As JSON is compared by its structure, AsJson() cannot be combined with IgnoringCase() or Using(comparer).

AsJson() is not available on Contains or DoesNotContain for a string subject, as parts of a JSON string are usually not valid JSON. Use IsEqualTo(expected).AsJson() or IsValidJsonMatching(expected) instead. On a collection of strings, Contains(expected).AsJson() compares each item as JSON.

Validation​

You can verify that a string is valid JSON:

string track = """{"title": "Let It Be"}""";

await Expect.That(track).IsValidJson();

If the closing brace was missing, the expectation would fail with:

Failure message
Expected that track
is valid JSON,
but it could not be parsed: Expected depth to be zero at the end of the JSON payload. There is an open JSON object or array that should be closed. LineNumber: 0 | BytePositionInLine: 21.

This verifies that the string can be parsed by JsonDocument.Parse without exceptions. You can also specify the JsonDocumentOptions:

string track = """{"title": "Let It Be"}""";

await Expect.That(track).IsValidJson(o => o with { CommentHandling = JsonCommentHandling.Disallow });

You can add further expectations on the JsonElement parsed from the subject:

string track = """{"title": "Let It Be"}""";

await Expect.That(track).IsValidJson().Which(j => j.Matches(new { title = "Let It Be" }));

Or you can verify that the string is valid JSON and matches an expected value in one step:

string track = """{"title": "Let It Be", "artist": "The Beatles"}""";

await Expect.That(track).IsValidJsonMatching(new { title = "Let It Be" });
await Expect.That(track).IsValidJsonMatchingExactly(new { title = "Let It Be", artist = "The Beatles" });

IsValidJsonMatching(new { title = "Yesterday" }) would fail with:

Failure message
Expected that track
is valid JSON which matches new { title = "Yesterday" },
but it differed as $.title was "Let It Be" instead of "Yesterday"

JsonElement​

Matches​

You can verify that a JsonElement matches an expected object. Matches ignores additional properties, while MatchesExactly also fails for them:

JsonElement track = JsonDocument.Parse("""{"title": "Let It Be", "artist": "The Beatles"}""").RootElement;

await Expect.That(track).Matches(new { title = "Let It Be" });
await Expect.That(track).MatchesExactly(new { title = "Let It Be", artist = "The Beatles" });

MatchesExactly(new { title = "Let It Be" }) would fail because of the additional artist property:

Failure message
Expected that track
matches new { title = "Let It Be" } exactly,
but it differed as $.artist had unexpected "The Beatles"

You can verify that a JsonElement matches an expected array:

JsonElement titles = JsonDocument.Parse("""["Let It Be", "Yesterday", "Hey Jude"]""").RootElement;

await Expect.That(titles).Matches(["Let It Be", "Yesterday"]);
await Expect.That(titles).MatchesExactly(["Let It Be", "Yesterday", "Hey Jude"]);

You can also verify that a JsonElement matches a primitive value:

await Expect.That(JsonDocument.Parse("\"Let It Be\"").RootElement).Matches("Let It Be");
await Expect.That(JsonDocument.Parse("4.05").RootElement).Matches(4.05);
await Expect.That(JsonDocument.Parse("true").RootElement).Matches(true);
await Expect.That(JsonDocument.Parse("null").RootElement).Matches(null);

IsObject​

You can verify that a JsonElement is a JSON object that satisfies some expectations:

JsonElement track = JsonDocument.Parse("""{"title": "Let It Be", "artist": "The Beatles"}""").RootElement;

await Expect.That(track).IsObject(o => o
.With("title").Matching("Let It Be").And
.With("artist").Matching("The Beatles"));

With .With("artist").Matching("The Rolling Stones") instead, the expectation would fail with:

Failure message
Expected that track
is an object and $.title matches "Let It Be" and $.artist matches "The Rolling Stones",
but it differed as $.artist was "The Beatles" instead of "The Rolling Stones"

You can verify that a property is another object, recursively:

JsonElement track = JsonDocument.Parse(
"""{"title": "Let It Be", "album": {"title": "Let It Be", "year": 1970}}""").RootElement;

await Expect.That(track).IsObject(o => o
.With("album").AnObject(album => album
.With("year").Matching(1970)));

You can verify that a property is an array:

JsonElement album = JsonDocument.Parse(
"""{"title": "Abbey Road", "tracks": ["Come Together", "Something"]}""").RootElement;

await Expect.That(album).IsObject(o => o
.With("tracks").AnArray(tracks => tracks.WithElements("Come Together", "Something")));

You can verify the number of properties in a JSON object:

JsonElement track = JsonDocument.Parse("""{"title": "Let It Be", "artist": "The Beatles"}""").RootElement;

await Expect.That(track).IsObject(o => o.With(2).Properties());

IsArray​

You can verify that a JsonElement is a JSON array that satisfies some expectations:

JsonElement titles = JsonDocument.Parse("""["Let It Be", "Yesterday"]""").RootElement;

await Expect.That(titles).IsArray(a => a
.At(0).Matching("Let It Be").And
.At(1).Matching("Yesterday"));

You can verify the number of elements in a JSON array:

JsonElement titles = JsonDocument.Parse("""["Let It Be", "Yesterday"]""").RootElement;

await Expect.That(titles).IsArray(a => a.With(2).Elements());

You can match the expected elements of an array directly:

JsonElement titles = JsonDocument.Parse("""["Let It Be", "Yesterday"]""").RootElement;

await Expect.That(titles).IsArray(a => a.WithElements("Let It Be", "Yesterday"));

With WithElements("Let It Be", "Hey Jude") instead, the expectation would fail with:

Failure message
Expected that titles
is an array and $[0] matches "Let It Be" and $[1] matches "Hey Jude",
but it differed as $[1] was "Yesterday" instead of "Hey Jude"

You can match nested arrays recursively (use null to skip an element):

JsonElement sides = JsonDocument.Parse(
"""[["Come Together", "Something"], ["Here Comes the Sun"], ["The End", "Her Majesty"]]""").RootElement;

await Expect.That(sides).IsArray(a => a
.WithArrays(
side => side.WithElements("Come Together", "Something"),
null,
side => side.At(1).Matching("Her Majesty")));

You can match objects in an array recursively (use null to skip an element):

JsonElement tracks = JsonDocument.Parse(
"""
[
{"title": "Let It Be"},
{"title": "Yesterday"},
{"title": "Hey Jude", "durationSeconds": 431}
]
""").RootElement;

await Expect.That(tracks).IsArray(a => a
.WithObjects(
track => track.With("title").Matching("Let It Be"),
null,
track => track.With(2).Properties()));

JSON serializable​

You can verify that an object is JSON serializable:

public record Track(string Title, string Artist);

Track track = new("Let It Be", "The Beatles");

await Expect.That(track).IsJsonSerializable();

This verifies that the object can be serialized to JSON and deserialized again, and that the result is equivalent to the original object. For example, a property with a private setter loses its value:

public class PlayCounter
{
public string Title { get; set; } = "";
public int PlayCount { get; private set; }
public void Play() => PlayCount++;
}

PlayCounter counter = new() { Title = "Let It Be" };
counter.Play();

await Expect.That(counter).IsJsonSerializable();
Failure message
Expected that counter
is serializable as JSON,
but it was not:
Property PlayCount differed:
Actual: 0
Expected: 1

You can specify both the JsonSerializerOptions and the equivalency options:

await Expect.That(track).IsJsonSerializable(
new JsonSerializerOptions { IncludeFields = true },
e => e.IgnoringMember(nameof(Track.Artist)));

You can also specify the type that the subject is deserialized into:

object track = new Track("Let It Be", "The Beatles");

await Expect.That(track).IsJsonSerializable<Track>(
new JsonSerializerOptions { IncludeFields = true },
e => e.IgnoringMember(nameof(Track.Artist)));

Customization​

You can change the default JsonDocumentOptions, used to parse JSON strings, and the default JsonSerializerOptions, used to format JSON values. Both allow trailing commas by default. Each value is set on its own and restored when the returned lifetime is disposed:

using (Customize.aweXpect.Json().DefaultJsonDocumentOptions.Set(new JsonDocumentOptions
{
AllowTrailingCommas = false,
}))
{
// parses JSON strings without allowing trailing commas
}

To change a value for all tests, set it on Customize.aweXpect.Global.Json() instead.