Skip to content

Commit

Permalink
Introduced the [CustomAssertionsAssembly] to mark an entire assembly …
Browse files Browse the repository at this point in the history
…as one that contains custom assertions.
  • Loading branch information
dennisdoomen committed Oct 18, 2023
1 parent 514ed4c commit cd6bb2a
Show file tree
Hide file tree
Showing 14 changed files with 111 additions and 6 deletions.
9 changes: 9 additions & 0 deletions FluentAssertions.sln
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "VB.Specs", "Tests\VB.Specs\
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Specs", "Tests\FSharp.Specs\FSharp.Specs.fsproj", "{0A69DC62-CA14-44E5-BAF9-2EB2E2E2CADF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExampleExtensions", "Tests\ExampleExtensions\ExampleExtensions.csproj", "{8DF4A6FE-AAD0-41E5-B2F4-34166D1B139C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
CI|Any CPU = CI|Any CPU
Expand Down Expand Up @@ -141,6 +143,12 @@ Global
{0A69DC62-CA14-44E5-BAF9-2EB2E2E2CADF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0A69DC62-CA14-44E5-BAF9-2EB2E2E2CADF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0A69DC62-CA14-44E5-BAF9-2EB2E2E2CADF}.Release|Any CPU.Build.0 = Release|Any CPU
{8DF4A6FE-AAD0-41E5-B2F4-34166D1B139C}.CI|Any CPU.ActiveCfg = Debug|Any CPU
{8DF4A6FE-AAD0-41E5-B2F4-34166D1B139C}.CI|Any CPU.Build.0 = Debug|Any CPU
{8DF4A6FE-AAD0-41E5-B2F4-34166D1B139C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8DF4A6FE-AAD0-41E5-B2F4-34166D1B139C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8DF4A6FE-AAD0-41E5-B2F4-34166D1B139C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8DF4A6FE-AAD0-41E5-B2F4-34166D1B139C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -160,6 +168,7 @@ Global
{A946043D-D3F8-46A4-B485-A88412C417FE} = {963262D0-9FD5-4741-8C0E-E2F34F110EF3}
{0C0211B6-D185-4518-A15A-38AC092EDC50} = {963262D0-9FD5-4741-8C0E-E2F34F110EF3}
{0A69DC62-CA14-44E5-BAF9-2EB2E2E2CADF} = {963262D0-9FD5-4741-8C0E-E2F34F110EF3}
{8DF4A6FE-AAD0-41E5-B2F4-34166D1B139C} = {963262D0-9FD5-4741-8C0E-E2F34F110EF3}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {75DDA3D8-9D6F-4865-93F4-DDE11DEE8290}
Expand Down
12 changes: 11 additions & 1 deletion Src/FluentAssertions/CallerIdentifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading;
using FluentAssertions.CallerIdentification;
Expand Down Expand Up @@ -137,7 +138,16 @@ internal static bool OnlyOneFluentAssertionScopeOnCallStack()

private static bool IsCustomAssertion(StackFrame frame)
{
return frame.GetMethod()?.IsDecoratedWithOrInherit<CustomAssertionAttribute>() == true;
MethodBase getMethod = frame.GetMethod();

if (getMethod is not null)
{
return
getMethod.IsDecoratedWithOrInherit<CustomAssertionAttribute>() ||
getMethod.ReflectedType?.Assembly.IsDefined(typeof(CustomAssertionsAssemblyAttribute)) == true;
}

return false;
}

private static bool IsDynamic(StackFrame frame)
Expand Down
12 changes: 12 additions & 0 deletions Src/FluentAssertions/CustomAssertionsAssemblyAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

namespace FluentAssertions;

/// <summary>
/// Marks an assembly as containing extensions to Fluent Assertions that either uses the built-in assertions
/// internally, or directly uses the <c>Execute.Assertion</c>.
/// </summary>
[AttributeUsage(AttributeTargets.Assembly)]
public sealed class CustomAssertionsAssemblyAttribute : Attribute
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,11 @@ namespace FluentAssertions
{
public CustomAssertionAttribute() { }
}
[System.AttributeUsage(System.AttributeTargets.Assembly)]
public sealed class CustomAssertionsAssemblyAttribute : System.Attribute
{
public CustomAssertionsAssemblyAttribute() { }
}
public static class EnumAssertionsExtensions
{
public static FluentAssertions.Primitives.EnumAssertions<TEnum> Should<TEnum>(this TEnum @enum)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,11 @@ namespace FluentAssertions
{
public CustomAssertionAttribute() { }
}
[System.AttributeUsage(System.AttributeTargets.Assembly)]
public sealed class CustomAssertionsAssemblyAttribute : System.Attribute
{
public CustomAssertionsAssemblyAttribute() { }
}
public static class EnumAssertionsExtensions
{
public static FluentAssertions.Primitives.EnumAssertions<TEnum> Should<TEnum>(this TEnum @enum)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@ namespace FluentAssertions
{
public CustomAssertionAttribute() { }
}
[System.AttributeUsage(System.AttributeTargets.Assembly)]
public sealed class CustomAssertionsAssemblyAttribute : System.Attribute
{
public CustomAssertionsAssemblyAttribute() { }
}
public static class EnumAssertionsExtensions
{
public static FluentAssertions.Primitives.EnumAssertions<TEnum> Should<TEnum>(this TEnum @enum)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,11 @@ namespace FluentAssertions
{
public CustomAssertionAttribute() { }
}
[System.AttributeUsage(System.AttributeTargets.Assembly)]
public sealed class CustomAssertionsAssemblyAttribute : System.Attribute
{
public CustomAssertionsAssemblyAttribute() { }
}
public static class EnumAssertionsExtensions
{
public static FluentAssertions.Primitives.EnumAssertions<TEnum> Should<TEnum>(this TEnum @enum)
Expand Down
3 changes: 3 additions & 0 deletions Tests/ExampleExtensions/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using FluentAssertions;

[assembly: CustomAssertionsAssembly]
16 changes: 16 additions & 0 deletions Tests/ExampleExtensions/ExampleExtensions.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<IsPackable>false</IsPackable>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>..\..\Src\FluentAssertions\FluentAssertions.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\Src\FluentAssertions\FluentAssertions.csproj" />
</ItemGroup>

</Project>
16 changes: 16 additions & 0 deletions Tests/ExampleExtensions/StringAssertionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using FluentAssertions;
using FluentAssertions.Primitives;

namespace ExampleExtensions;

public static class StringAssertionExtensions
{
public static void BePalindromic(this StringAssertions assertions)
{
char[] charArray = assertions.Subject.ToCharArray();
Array.Reverse(charArray);
string reversedSubject = new string(charArray);

assertions.Subject.Should().Be(reversedSubject);
}
}
23 changes: 19 additions & 4 deletions Tests/FluentAssertions.Specs/ExtensibilitySpecs.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using ExampleExtensions;
using Xunit;
using Xunit.Sdk;

Expand All @@ -7,7 +8,7 @@ namespace FluentAssertions.Specs;
public class ExtensibilitySpecs
{
[Fact]
public void When_a_method_is_marked_as_custom_assertion_it_should_be_ignored_during_caller_identification()
public void Methods_marked_as_custom_assertion_are_ignored_during_caller_identification()
{
// Arrange
var myClient = new MyCustomer
Expand All @@ -22,22 +23,36 @@ public void When_a_method_is_marked_as_custom_assertion_it_should_be_ignored_dur
act.Should().Throw<XunitException>().WithMessage(
"Expected myClient to be true because we don't work with old clients, but found False.");
}

[Fact]
public void Methods_in_assemblies_marked_as_custom_assertion_are_ignored_during_caller_identification()
{
// Arrange
string palindrome = "fluent";

// Act
Action act = () => palindrome.Should().BePalindromic();

// Assert
act.Should().Throw<XunitException>().WithMessage(
"Expected palindrome to be*tneulf*");
}
}

public class MyCustomer
internal class MyCustomer
{
public bool Active { get; set; }
}

public static class MyCustomerExtensions
internal static class MyCustomerExtensions
{
public static MyCustomerAssertions Should(this MyCustomer customer)
{
return new MyCustomerAssertions(customer);
}
}

public class MyCustomerAssertions
internal class MyCustomerAssertions
{
private readonly MyCustomer customer;

Expand Down
3 changes: 2 additions & 1 deletion Tests/FluentAssertions.Specs/FluentAssertions.Specs.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net47;net6.0</TargetFrameworks>
Expand Down Expand Up @@ -45,6 +45,7 @@
<ProjectReference Include="..\..\Src\FluentAssertions\FluentAssertions.csproj" />
<ProjectReference Include="..\AssemblyA\AssemblyA.csproj" />
<ProjectReference Include="..\AssemblyB\AssemblyB.csproj" />
<ProjectReference Include="..\ExampleExtensions\ExampleExtensions.csproj" />
</ItemGroup>

</Project>
2 changes: 2 additions & 0 deletions docs/_pages/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ myClient.Should().BeActive("because we don't work with old clients");

Without the `[CustomAssertion]` attribute, Fluent Assertions would find the line that calls `Should().BeTrue()` and treat the `customer` variable as the subject-under-test (SUT). But by applying this attribute, it will ignore this invocation and instead find the SUT by looking for a call to `Should().BeActive()` and use the `myClient` variable instead.

Alternatively, you can add the `[assembly:CustomAssertionsAssembly]` attribute to a file within the project to tell Fluent Assertions that all code in that assembly should be treated as custom assertion code.

## Assertion Scopes

You can batch multiple assertions into an `AssertionScope` so that FluentAssertions throws one exception at the end of the scope with all failures.
Expand Down
1 change: 1 addition & 0 deletions docs/_pages/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ sidebar:

### Improvements
* Improve failure message for string assertions when checking for equality - [#2307](https://github.com/fluentassertions/fluentassertions/pull/2307)
* You can mark all assertions in an assembly as custom assertions using the `[CustomAssertionsAssembly]` attribute - [#2389](https://github.com/fluentassertions/fluentassertions/pull/2389)

### Fixes
* Fixed formatting error when checking nullable `DateTimeOffset` with
Expand Down

0 comments on commit cd6bb2a

Please sign in to comment.