Skip to content

Commit

Permalink
fluentExtensions helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasteles committed Jul 6, 2023
1 parent d70cd39 commit e8ea754
Show file tree
Hide file tree
Showing 17 changed files with 299 additions and 8 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,14 @@ Result<int, string> resultWithDelay2 = await results[0].Select(async n =>
await Task.Delay(n);
return n;
}).ToTask();
```

# FluentAssertions Extensions

[NuGet package](https://www.nuget.org/packages/Resulteles.FluentAssertions) available:

```ps
$ dotnet add package Resulteles.FluentAssertions
```

# Alternatives
Expand Down
19 changes: 13 additions & 6 deletions Resulteles.sln
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "_build", "build\_build.cspr
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Resulteles.Tests", "tests\Resulteles.Tests\Resulteles.Tests.csproj", "{83E7BE20-8510-48FC-9611-711CA767711F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Resulteles", "src\Resulteles.csproj", "{85DBCC03-B7CE-4B3F-BFB7-9984653C488A}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Resulteles", "src\Resulteles\Resulteles.csproj", "{C4AC93F4-5F57-4124-99EE-036AEFD51AB1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Resulteles.FluentAssertions", "src\Resulteles.FluentAssertions\Resulteles.FluentAssertions.csproj", "{F115CBF0-9D30-4A92-8BCE-85F58E7607C1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -25,10 +27,14 @@ Global
{83E7BE20-8510-48FC-9611-711CA767711F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{83E7BE20-8510-48FC-9611-711CA767711F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{83E7BE20-8510-48FC-9611-711CA767711F}.Release|Any CPU.Build.0 = Release|Any CPU
{85DBCC03-B7CE-4B3F-BFB7-9984653C488A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{85DBCC03-B7CE-4B3F-BFB7-9984653C488A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{85DBCC03-B7CE-4B3F-BFB7-9984653C488A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{85DBCC03-B7CE-4B3F-BFB7-9984653C488A}.Release|Any CPU.Build.0 = Release|Any CPU
{C4AC93F4-5F57-4124-99EE-036AEFD51AB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C4AC93F4-5F57-4124-99EE-036AEFD51AB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C4AC93F4-5F57-4124-99EE-036AEFD51AB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C4AC93F4-5F57-4124-99EE-036AEFD51AB1}.Release|Any CPU.Build.0 = Release|Any CPU
{F115CBF0-9D30-4A92-8BCE-85F58E7607C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F115CBF0-9D30-4A92-8BCE-85F58E7607C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F115CBF0-9D30-4A92-8BCE-85F58E7607C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F115CBF0-9D30-4A92-8BCE-85F58E7607C1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -38,6 +44,7 @@ Global
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{83E7BE20-8510-48FC-9611-711CA767711F} = {18DDE0D0-38EA-4936-8AD2-03ED44A38053}
{85DBCC03-B7CE-4B3F-BFB7-9984653C488A} = {0BAC928C-339F-4FB4-A854-386C901B6B0F}
{C4AC93F4-5F57-4124-99EE-036AEFD51AB1} = {0BAC928C-339F-4FB4-A854-386C901B6B0F}
{F115CBF0-9D30-4A92-8BCE-85F58E7607C1} = {0BAC928C-339F-4FB4-A854-386C901B6B0F}
EndGlobalSection
EndGlobal
201 changes: 201 additions & 0 deletions src/Resulteles.FluentAssertions/ResultAssertions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
using FluentAssertions;
using FluentAssertions.Equivalency;
using FluentAssertions.Execution;
using FluentAssertions.Primitives;

#pragma warning disable CS1591

namespace Resulteles;

using ResultT = Result<Success, string>;

/// <inheritdoc />
public class ResultSimpleAssertions
: ReferenceTypeAssertions<ResultT, ResultSimpleAssertions>
{
/// <inheritdoc />
protected override string Identifier => "Result";

/// <inheritdoc />
public ResultSimpleAssertions(ResultT subject) : base(subject) { }

public AndConstraint<ResultSimpleAssertions> BeOk()
{
Execute.Assertion
.Given(() => Subject)
.ForCondition(status => status.IsOk)
.FailWith(
"Expected {context:Result} to be Ok, but found Error {0}.",
sub => sub.Value);

return new(this);
}

public AndConstraint<ResultSimpleAssertions> BeError()
{
Execute.Assertion
.Given(() => Subject)
.ForCondition(status => status.IsError)
.FailWith("Expected {context:Result} to be Error, but found Ok.");
return new(this);
}

public AndConstraint<ResultSimpleAssertions> HaveMessage(string message)
{
Execute.Assertion
.Given(() => Subject)
.ForCondition(status => status.Value is string err && err == message)
.FailWith("Expected {context:Result} to have message {0}, but found {1}.",
_ => message, sub => sub.Value);

return new(this);
}

public AndConstraint<ResultSimpleAssertions> BeErrorWithMessage(string message) =>
BeError().And.HaveMessage(message);
}

/// <inheritdoc />
public class ResultTStringAssertions<T>
: ReferenceTypeAssertions<Result<T, string>, ResultTStringAssertions<T>>
{
/// <inheritdoc />
protected override string Identifier => "ResultT";

readonly ResultSimpleAssertions baseSimpleAssertion;

/// <inheritdoc />
public ResultTStringAssertions(Result<T, string> subject) : base(subject) =>
baseSimpleAssertion = new(subject.Select(_ => new Success()));


public AndConstraint<ResultTStringAssertions<T>> BeOk()
{
baseSimpleAssertion.BeOk();
return new(this);
}

public AndConstraint<ResultTStringAssertions<T>> BeError()
{
baseSimpleAssertion.BeError();
return new(this);
}

public AndConstraint<ResultTStringAssertions<T>> HaveMessage(string message)
{
baseSimpleAssertion.HaveMessage(message);
return new(this);
}

public AndConstraint<ResultTStringAssertions<T>> ErrorWithMessage(string message) =>
BeError().And.HaveMessage(message);

public AndConstraint<ResultTStringAssertions<T>> Be(T other)
{
Subject.Value.Should().Be(other);
return new(this);
}

public AndConstraint<ResultTStringAssertions<T>> BeOkThen(Action<T> action)
{
baseSimpleAssertion.BeOk();
action((T)Subject.Value);
return new(this);
}

public AndConstraint<ResultTStringAssertions<T>> BeEquivalentTo<TOther>(TOther other)
{
Subject.Value.Should().BeEquivalentTo(other);
return new(this);
}

public AndConstraint<ResultTStringAssertions<T>> BeEquivalentTo<TOther>(TOther other,
Func<EquivalencyAssertionOptions<TOther>, EquivalencyAssertionOptions<TOther>> options)
{
Subject.Value.Should().BeEquivalentTo(other, options);
return new(this);
}
}

/// <inheritdoc />
public class ResultTypeAssertions<TOk, TError>
: ReferenceTypeAssertions<Result<TOk, TError>, ResultTypeAssertions<TOk, TError>>
{
/// <inheritdoc />
protected override string Identifier => "ResultType";

/// <inheritdoc />
public ResultTypeAssertions(Result<TOk, TError> subject) : base(subject) { }

public AndConstraint<ResultTypeAssertions<TOk, TError>> BeOk()
{
Execute.Assertion
.Given(() => Subject)
.ForCondition(status => status.IsOk)
.FailWith(
"Expected {context:Result} to be Ok, but found Error {0}.",
sub => sub.Value);

return new(this);
}

public AndConstraint<ResultTypeAssertions<TOk, TError>> BeOk(TOk okValue)
{
Execute.Assertion
.Given(() => Subject)
.ForCondition(sub => sub.IsOk && sub.Value.Equals(okValue))
.FailWith(
"Expected {context:Result} to be Ok with value {0}, but found Error {1}.",
_ => okValue, sub => sub.Value
);

return new(this);
}

public AndConstraint<ResultTypeAssertions<TOk, TError>> BeError()
{
Execute.Assertion
.Given(() => Subject)
.ForCondition(status => status.IsError)
.FailWith("Expected {context:Result} to be Error, but found Ok.");
return new(this);
}

public AndConstraint<ResultTypeAssertions<TOk, TError>> BeError(TError errorValue)
{
Execute.Assertion
.Given(() => Subject)
.ForCondition(sub => sub.IsError && sub.Value.Equals(errorValue))
.FailWith("Expected {context:Result} to be Error {0}, but found Ok {1}",
_ => errorValue, s => s.Value
);
return new(this);
}

public AndConstraint<ResultTypeAssertions<TOk, TError>> BeEquivalentTo<TOther>(TOther other)
{
Subject.Value.Should().BeEquivalentTo(other);
return new(this);
}

public AndConstraint<ResultTypeAssertions<TOk, TError>> BeEquivalentTo<TOther>(TOther other,
Func<EquivalencyAssertionOptions<TOther>, EquivalencyAssertionOptions<TOther>> options)
{
Subject.Value.Should().BeEquivalentTo(other, options);
return new(this);
}

public AndConstraint<ResultTypeAssertions<TOk, TError>> BeOkThen(Action<TOk> action)
{
BeOk();
action((TOk)Subject.Value);
return new(this);
}

public AndConstraint<ResultTypeAssertions<TOk, TError>> BeErrorThen(Action<TError> action)
{
BeError();
action((TError)Subject.Value);
return new(this);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Resulteles;

/// <summary>
/// Result assertion extensions
/// </summary>
public static class ResultFluentAssertionsExtensions
{
/// <summary>
/// Simple result assertions
/// </summary>
public static ResultSimpleAssertions Should(this Result<Success, string> result) => new(result);

/// <summary>
/// String Error result assertions
/// </summary>
public static ResultTStringAssertions<T> Should<T>(this Result<T, string> result) => new(result);

/// <summary>
/// Result assertions
/// </summary>
public static ResultTypeAssertions<TOk, TError> Should<TOk, TError>(this Result<TOk, TError> result) => new(result);
}
47 changes: 47 additions & 0 deletions src/Resulteles.FluentAssertions/Resulteles.FluentAssertions.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<PackageId>Resulteles.FluentAssertions</PackageId>
<Authors>Lucas Teles - [email protected]</Authors>
<Company/>
<Description>FluentAssertions extensions for Resulteles</Description>
<RepositoryType>GitHub</RepositoryType>
<PackageProjectUrl>https://github.com/lucasteles/Resulteles</PackageProjectUrl>
<RepositoryUrl>https://github.com/lucasteles/Resulteles</RepositoryUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>

<RootNamespace>Resulteles</RootNamespace>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PackageTags>extensions, tools, helpers, utils</PackageTags>
<DocumentationFile>bin\$(Configuration)\$(AssemblyName).xml</DocumentationFile>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
</PropertyGroup>

<PropertyGroup>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\README.md" Pack="true" PackagePath="\"/>
</ItemGroup>
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.11.0"/>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Resulteles\Resulteles.csproj"/>
</ItemGroup>

</Project>
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion src/Resulteles.csproj → src/Resulteles/Resulteles.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<None Include="..\README.md" Pack="true" PackagePath="\"/>
<None Include="..\..\README.md" Pack="true" PackagePath="\"/>
</ItemGroup>
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
Expand Down
3 changes: 3 additions & 0 deletions src/Resulteles/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
global using System;
global using System.Collections.Generic;
global using System.Linq;
2 changes: 1 addition & 1 deletion tests/Resulteles.Tests/Resulteles.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Resulteles.csproj" />
<ProjectReference Include="..\..\src\Resulteles\Resulteles.csproj" />
</ItemGroup>

</Project>
4 changes: 4 additions & 0 deletions tests/Resulteles.Tests/Samples.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
using System.Diagnostics.CodeAnalysis;

namespace Resulteles.Tests;

[SuppressMessage("Minor Code Smell", "S1481:Unused local variables should be removed")]
[SuppressMessage("ReSharper", "VariableHidesOuterVariable")]
public class Samples
{
public static void Teste()
Expand Down

0 comments on commit e8ea754

Please sign in to comment.