diff --git a/Src/FluentAssertions/FluentAssertions.csproj b/Src/FluentAssertions/FluentAssertions.csproj index f295879aeb..8df862b471 100644 --- a/Src/FluentAssertions/FluentAssertions.csproj +++ b/Src/FluentAssertions/FluentAssertions.csproj @@ -70,6 +70,7 @@ + @@ -79,6 +80,7 @@ + @@ -89,6 +91,7 @@ + @@ -98,6 +101,7 @@ + @@ -106,6 +110,7 @@ + diff --git a/Src/FluentAssertions/Json/JsonElementAssertions.cs b/Src/FluentAssertions/Json/JsonElementAssertions.cs index fcb0da6902..3538e3d4d0 100644 --- a/Src/FluentAssertions/Json/JsonElementAssertions.cs +++ b/Src/FluentAssertions/Json/JsonElementAssertions.cs @@ -86,7 +86,7 @@ public AndConstraint BeString(string value, string becaus /// /// Asserts that the current is the JSON number node. /// - /// The value of the JSON string node. + /// The value of the JSON number node. /// /// 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. @@ -104,6 +104,10 @@ public AndConstraint BeNumber(decimal value, string becau return new(this); } + /// + public AndConstraint BeNumber(long value, string because = "", params object[] becauseArgs) + => BeNumber((decimal)value, because, becauseArgs); + /// /// Asserts that the current is the JSON true node. /// @@ -144,11 +148,5 @@ public AndConstraint BeFalse(string because = "", params return new(this); } - public void Be(long jsonNumber) - { - Execute.Assertion - .ForCondition(Subject.ValueKind == JsonValueKind.Number - && Subject.GetInt64() == jsonNumber) - .FailWith("Expected {context:JSON} to be a number with value {0}, but got {1} instead.", jsonNumber, Subject.GetInt64()); - } + } diff --git a/Src/FluentAssertions/Json/JsonSerializerOptionsAssertions.cs b/Src/FluentAssertions/Json/JsonSerializerOptionsAssertions.cs index bcb5fe24e2..1ff4268cf4 100644 --- a/Src/FluentAssertions/Json/JsonSerializerOptionsAssertions.cs +++ b/Src/FluentAssertions/Json/JsonSerializerOptionsAssertions.cs @@ -1,5 +1,7 @@ using System; using System.Diagnostics; +using System.IO; +using System.Text; using System.Text.Json; using FluentAssertions.Common; using FluentAssertions.Execution; @@ -37,7 +39,7 @@ public JsonSerializerOptionsAssertions(JsonSerializerOptions subject) /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint> Deserialize(string json, string because = "", params object[] becauseArgs) + public AndConstraint> Deserialize(Stream json, string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(json); @@ -55,6 +57,13 @@ public AndConstraint> Deserialize(string json, string because return new(new(deserialzed)); } + /// + public AndConstraint> Deserialize(string json, string because = "", params object[] becauseArgs) + { + Stream stream = json is null ? null : new MemoryStream(Encoding.UTF8.GetBytes(json)); + return Deserialize(stream, because, becauseArgs); + } + /// /// Asserts that the current can be used to serialize the specified value. /// @@ -83,7 +92,7 @@ public AndConstraint> Serialize(T value, string bec return new(new(serialized)); } - private T TryDeserialize(string json, out Exception failure) + private T TryDeserialize(Stream json, out Exception failure) { try { diff --git a/docs/_pages/json.md b/docs/_pages/json.md new file mode 100644 index 0000000000..ee6be1cb2e --- /dev/null +++ b/docs/_pages/json.md @@ -0,0 +1,103 @@ +--- +title: Streams +permalink: /json/ +layout: single +classes: wide +sidebar: + nav: "sidebar" +--- + +## JSON ## +A lot (if not the vast majority) of applications use JSON (de)serialization. +By doing so, how the serialization is performed is part of the public contract/API. +To prevent unexpected changes, writing tests that ensure the behavior is important. + +### JSON serialization options +To ensure that the assertions are done on the serialization options that are registered, it might be worth explicitly resolve those options: + +```csharp +public class Serialization : Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory +{ + private JsonSerializerOptions SerializerOptions => Services.GetRequiredService(); +} +``` + +Both a `Serialize()` and a `Deserialize()` can be performed: + +```csharp +JsonSerializerOptions options = GetOptions(); + +options.Should().Serialize(someObject); +options.Should().Deserialize(someJson); +``` + +And because we specially interested in the results of these actions, we want to assert the outcomes to: + +```csharp +public class Serializes +{ + [Fact] + public void DateOnly_as_string_value() + { + JsonSerializerOptions options = GetOptions(); + + options.Should().Serialize(new DateOnly(2017, 06, 11)) + .And.Value.Should().BeString("2017-06-11"); + } + + [Fact] + public void Enum_as_number() + { + + JsonSerializerOptions options = GetOptions(); + + options.Should().Serialize(SomeEnum.SomeValue) + .And.Value.Should().BeNumber(42); + } + + [Fact] + pulic void Default_of_custom_struct_as_null() + { + JsonSerializerOptions options = GetOptions(); + + options.Should().Serialize(default(CustomStruct)) + .And.Value.Should().BeNull(); + } +} +``` + +And + +```csharp +public class Deserializes +{ + [Fact] + public void DateOnly_from_JSON_string() + { + JsonSerializerOptions options = GetOptions(); + + options.Should().Deserialize("2017-06-11") + .And.Value.Should().Be(new DateOnly(2017-06-11)); + } + + [Fact] + public void Enum_from_number() + { + + JsonSerializerOptions options = GetOptions(); + + options.Should().Deserialize("42") + .And.Value.Should().BeNumber(SomeEnum.SomeValue); + } + + [Fact] + public void Enum_from_string() + { + + JsonSerializerOptions options = GetOptions(); + + options.Should().Deserialize("\"SomeValue\"") + .And.Value.Should().BeNumber(SomeEnum.SomeValue); + } +} +``` \ No newline at end of file