Skip to content

Commit

Permalink
Update documentation, and test coverage.
Browse files Browse the repository at this point in the history
  • Loading branch information
Corniel committed Aug 5, 2023
1 parent 53c2638 commit b193be3
Show file tree
Hide file tree
Showing 8 changed files with 276 additions and 116 deletions.
133 changes: 83 additions & 50 deletions Src/FluentAssertions/FluentAssertions.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">

<!-- To reduce build times, we only enable analyzers for the newest TFM -->
<PropertyGroup>
<TargetFrameworks>net47;net6.0;netcoreapp2.1;netcoreapp3.0;netstandard2.0;netstandard2.1</TargetFrameworks>
Expand All @@ -11,7 +12,8 @@
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
</PropertyGroup>
<PropertyGroup>

<PropertyGroup Label="Package info">
<Authors>Dennis Doomen;Jonas Nyrup</Authors>
<PackageDescription>
A very extensive set of extension methods that allow you to more naturally specify the expected outcome of a TDD or
Expand All @@ -29,7 +31,8 @@
<PackageReleaseNotes>See https://fluentassertions.com/releases/</PackageReleaseNotes>
<Copyright>Copyright Dennis Doomen 2010-$([System.DateTime]::Now.ToString(yyyy))</Copyright>
</PropertyGroup>
<ItemGroup>

<ItemGroup Label="Internals visible to">
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>FluentAssertions.Specs, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d25ff515c85b13ba08f61d466cff5d80a7f28ba197bbf8796085213e7a3406f970d2a4874932fed35db546e89af2da88c194bf1b7f7ac70de7988c78406f7629c547283061282a825616eb7eb48a9514a7570942936020a9bb37dca9ff60b778309900851575614491c6d25018fadb75828f4c7a17bf2d7dc86e7b6eafc5d8f</_Parameter1>
</AssemblyAttribute>
Expand All @@ -40,58 +43,88 @@
<_Parameter1>Benchmarks, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d25ff515c85b13ba08f61d466cff5d80a7f28ba197bbf8796085213e7a3406f970d2a4874932fed35db546e89af2da88c194bf1b7f7ac70de7988c78406f7629c547283061282a825616eb7eb48a9514a7570942936020a9bb37dca9ff60b778309900851575614491c6d25018fadb75828f4c7a17bf2d7dc86e7b6eafc5d8f</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
<ItemGroup>
<None Remove="BannedSymbols.txt" />

<ItemGroup Label="Package files">
<None Include="..\FluentAssertions.png" Pack="true" Visible="false" PackagePath="" />
</ItemGroup>
<ItemGroup>

<ItemGroup Label="Analyzers">
<AdditionalFiles Include="BannedSymbols.txt" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2023.2.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
<PackageReference Include="PolySharp" Version="1.13.2" PrivateAssets="all" />
<None Include="..\FluentAssertions.png" Pack="true" Visible="false" PackagePath="" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<Compile Remove="Events/*.cs" />
<Compile Remove="EventRaisingExtensions.cs" />
</ItemGroup>
<ItemGroup Condition=" !('$(TargetFramework)' == 'net47' Or '$(TargetFramework)' == 'netstandard2.0') ">
<Compile Remove="SystemExtensions.cs" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net6.0' ">
<Compile Remove="StringBuilderExtensions.cs" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net47' Or '$(TargetFramework)' == 'netcoreapp2.1' Or '$(TargetFramework)' == 'netcoreapp3.0' Or '$(TargetFramework)' == 'net6.0' ">
<Compile Remove="Common/NullConfigurationStore.cs" />
</ItemGroup>
<ItemGroup Condition=" !('$(TargetFramework)' == 'net47' Or '$(TargetFramework)' == 'netcoreapp2.1' Or '$(TargetFramework)' == 'netcoreapp3.0' Or '$(TargetFramework)' == 'net6.0') ">
<Compile Remove="Common/AppSettingsConfigurationStore.cs" />
<Compile Remove="Common/ConfigurationStoreExceptionInterceptor.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.1'">
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
<PackageReference Include="System.Configuration.ConfigurationManager" Version="4.4.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'">
<PackageReference Include="System.Configuration.ConfigurationManager" Version="4.4.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
<PackageReference Include="System.Configuration.ConfigurationManager" Version="4.4.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net47'">
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.0" />
<TfmSpecificFrameworkAssemblyReferences Include="System.Net.Http">
<TargetFramework>$(TargetFramework)</TargetFramework>
</TfmSpecificFrameworkAssemblyReferences>
<Reference Include="System.Net.Http" />
<Reference Include="System.Configuration" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" />
</ItemGroup>


<!-- Target framework dependent configuration -->
<Choose>
<When Condition="'$(TargetFramework)' == 'net6.0'">
<ItemGroup>
<Compile Remove="Common/NullConfigurationStore.cs" />
<Compile Remove="StringBuilderExtensions.cs" />
<Compile Remove="SystemExtensions.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Configuration.ConfigurationManager" Version="4.4.0" />
</ItemGroup>
</When>
<When Condition="'$(TargetFramework)' == 'netcoreapp3.0'">
<ItemGroup>
<Compile Remove="Common/NullConfigurationStore.cs" />
<Compile Remove="Json/*.cs" />
<Compile Remove="SystemExtensions.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Configuration.ConfigurationManager" Version="4.4.0" />
</ItemGroup>
</When>
<When Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
<ItemGroup>
<Compile Remove="Common/NullConfigurationStore.cs" />
<Compile Remove="Json/*.cs" />
<Compile Remove="SystemExtensions.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Configuration.ConfigurationManager" Version="4.4.0" />
</ItemGroup>
</When>
<When Condition="'$(TargetFramework)' == 'netstandard2.1'">
<ItemGroup>
<Compile Remove="Common/AppSettingsConfigurationStore.cs" />
<Compile Remove="Common/ConfigurationStoreExceptionInterceptor.cs" />
<Compile Remove="Json/*.cs" />
<Compile Remove="SystemExtensions.cs" />
</ItemGroup>
</When>
<When Condition="'$(TargetFramework)' == 'netstandard2.0'">
<ItemGroup>
<Compile Remove="Common/AppSettingsConfigurationStore.cs" />
<Compile Remove="Common/ConfigurationStoreExceptionInterceptor.cs" />
<Compile Remove="Events/*.cs" />
<Compile Remove="EventRaisingExtensions.cs" />
<Compile Remove="Json/*.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.0" />
</ItemGroup>
</When>
<When Condition="'$(TargetFramework)' == 'net47'">
<ItemGroup>
<Compile Remove="Common/NullConfigurationStore.cs" />
<Compile Remove="Json/*.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.0" />
<TfmSpecificFrameworkAssemblyReferences Include="System.Net.Http">
<TargetFramework>$(TargetFramework)</TargetFramework>
</TfmSpecificFrameworkAssemblyReferences>
<Reference Include="System.Net.Http" />
<Reference Include="System.Configuration" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" />
</ItemGroup>
</When>
</Choose>

</Project>
8 changes: 0 additions & 8 deletions Src/FluentAssertions/Json/Deserialized.cs

This file was deleted.

9 changes: 4 additions & 5 deletions Src/FluentAssertions/Json/JsonElementAssertions.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#if NET6_0_OR_GREATER

using System.Text.Json;
using System.Text.Json;
using FluentAssertions.Execution;

namespace FluentAssertions.Json;

/// <summary>
/// Contains a number of methods to assert that an <see cref="JsonElement" /> is in the expected state.
/// </summary>
public class JsonElementAssertions
{
public JsonElement Subject { get; }
Expand All @@ -22,5 +23,3 @@ public void Be(long jsonNumber)
.FailWith("Expected {context:JSON} to be a number with value {0}, but got {1} instead.", jsonNumber, Subject.GetInt64());
}
}

#endif
111 changes: 90 additions & 21 deletions Src/FluentAssertions/Json/JsonSerializerOptionsAssertions.cs
Original file line number Diff line number Diff line change
@@ -1,46 +1,115 @@
#if NET6_0_OR_GREATER

using System;
using System.Diagnostics.Contracts;
using System;
using System.Diagnostics;
using System.Text.Json;
using FluentAssertions.Common;
using FluentAssertions.Execution;
using FluentAssertions.Primitives;

namespace FluentAssertions.Json;

/// <summary>
/// Contains a number of methods to assert that an <see cref="JsonSerializerOptions" /> is in the expected state.
/// </summary>
// [DebuggerNonUserCode]
public class JsonSerializerOptionsAssertions : ReferenceTypeAssertions<JsonSerializerOptions, JsonSerializerOptionsAssertions>
{
/// <summary>
/// Initializes a new instance of the <see cref="JsonSerializerOptionsAssertions" /> class.
/// </summary>
/// <param name="subject">The subject</param>
public JsonSerializerOptionsAssertions(JsonSerializerOptions subject)
: base(subject)
{
}

[Pure]
public AndConstraint<Deserialized<T>> Deserialize<T>(string json, string because = "", params object[] becauseArgs)
protected override string Identifier => "options";

/// <summary>
/// Asserts that the current <see cref="JsonSerializerOptions"/> can be used to deserialize the specified JSON string.
/// </summary>
/// <typeparam name="T">The type to serialize to.</typeparam>
/// <param name="json">The JSON string.</param>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <see paramref="because" />.
/// </param>
/// <exception cref="ArgumentNullException"><paramref name="json"/> is <see langword="null"/>.</exception>
public AndConstraint<ValueWrapper<T>> Deserialize<T>(string json, string because = "", params object[] becauseArgs)
{
Guard.ThrowIfArgumentIsNull(json);

Execute.Assertion
.ForCondition(Subject is { })
.FailWith("Can not use {context} to deserialize from JSON as it is <null>.");

T deserialzed = TryDeserialize<T>(json, out Exception failure);

Execute.Assertion
.BecauseOf(because, becauseArgs)
.ForCondition(failure is null)
.FailWith("Expected {context:the options} to deserialize {0}{reason}, but it failed: {1}.", json, failure?.Message);

return new(new(deserialzed));
}

/// <summary>
/// Asserts that the current <see cref="JsonSerializerOptions"/> can be used to serialize the specified value.
/// </summary>
/// <typeparam name="T">The type to serialize to.</typeparam>
/// <param name="value">The value to serialize.</param>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <see paramref="because" />.
/// </param>
public AndConstraint<ValueWrapper<JsonElement>> Serialize<T>(T value, string because = "", params object[] becauseArgs)
{
Execute.Assertion
.ForCondition(Subject is { })
.FailWith("Can not use {context} to serialize to JSON as it is <null>.");

JsonElement serialized = TrySerialize(value, out Exception failure);

Execute.Assertion
.BecauseOf(because, becauseArgs)
.ForCondition(failure is null)
.FailWith("Expected {context:the options} to serialize {0}{reason}, but it failed: {1}.", value, failure?.Message);

return new(new(serialized));
}

private T TryDeserialize<T>(string json, out Exception failure)
{
try
{
return new(new(JsonSerializer.Deserialize<T>(json, Subject)));
failure = null;
return JsonSerializer.Deserialize<T>(json, Subject);
}
catch (Exception exception)
{
Execute.Assertion
.BecauseOf(because, becauseArgs)
.FailWith("Expected {context:the options} to deserialize {0}{reason}, but it failed: {1}.", json, exception.Message);

throw;
failure = exception;
return default;
}
}

protected override string Identifier => "options";

[Pure]
public AndConstraint<Serialized> Serialize<T>(T value)
private JsonElement TrySerialize<T>(T value, out Exception failure)
{
var bytes = JsonSerializer.SerializeToUtf8Bytes(value, Subject);
using var doc = JsonDocument.Parse(bytes);
return new(new(doc.RootElement.Clone()));
try
{
failure = null;
var bytes = JsonSerializer.SerializeToUtf8Bytes(value, Subject);
using var doc = JsonDocument.Parse(bytes);
return doc.RootElement.Clone();
}
catch (Exception exception)
{
failure = exception;
return default;
}
}
}

#endif
17 changes: 0 additions & 17 deletions Src/FluentAssertions/Json/Serialized.cs

This file was deleted.

12 changes: 12 additions & 0 deletions Src/FluentAssertions/ValueWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

namespace FluentAssertions;

public sealed class ValueWrapper<TValue>
{
public ValueWrapper(TValue value) => Value = value;

public TValue Value { get; }

public override string ToString() => Value?.ToString();
}
4 changes: 4 additions & 0 deletions Tests/FluentAssertions.Specs/FluentAssertions.Specs.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
<Compile Remove="Events\*.cs" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' != 'net6.0' ">
<Compile Remove="Json\*.cs" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net47'">
<!--
(SRCU = System.Runtime.CompilerServices.Unsafe)
Expand Down

0 comments on commit b193be3

Please sign in to comment.