Skip to content

Commit

Permalink
Merge pull request #1847 from jnyrup/feature/jn/CoverageOfFakeShould
Browse files Browse the repository at this point in the history
Improve code coverage of fake should overloads
  • Loading branch information
jnyrup committed Mar 14, 2022
2 parents b66f77d + 3fced27 commit 6a4ee57
Show file tree
Hide file tree
Showing 10 changed files with 100 additions and 9 deletions.
24 changes: 17 additions & 7 deletions Build/Build.cs
Expand Up @@ -50,12 +50,15 @@ class Build : NukeBuild

AbsolutePath ArtifactsDirectory => RootDirectory / "Artifacts";

AbsolutePath TestResultsDirectory => RootDirectory / "TestResults";

string SemVer;

Target Clean => _ => _
.Executes(() =>
{
EnsureCleanDirectory(ArtifactsDirectory);
EnsureCleanDirectory(TestResultsDirectory);
});

Target CalculateNugetVersion => _ => _
Expand Down Expand Up @@ -140,7 +143,10 @@ class Build : NukeBuild
.SetConfiguration("Debug")
.EnableNoBuild()
.SetDataCollector("XPlat Code Coverage")
.SetResultsDirectory(RootDirectory / "TestResults")
.SetResultsDirectory(TestResultsDirectory)
.AddRunSetting(
"DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.DoesNotReturnAttribute",
"DoesNotReturnAttribute")
.CombineWith(
projects,
(_, project) => _
Expand All @@ -160,14 +166,15 @@ class Build : NukeBuild
{
ReportGenerator(s => s
.SetProcessToolPath(ToolPathResolver.GetPackageExecutable("ReportGenerator", "ReportGenerator.dll", framework: "net6.0"))
.SetTargetDirectory(RootDirectory / "TestResults" / "reports")
.AddReports(RootDirectory / "TestResults/**/coverage.cobertura.xml")
.SetTargetDirectory(TestResultsDirectory / "reports")
.AddReports(TestResultsDirectory / "**/coverage.cobertura.xml")
.AddReportTypes("HtmlInline_AzurePipelines_Dark", "lcov")
.SetClassFilters("-System.Diagnostics.CodeAnalysis.StringSyntaxAttribute")
.SetClassFilters(
"-System.Diagnostics.CodeAnalysis.StringSyntaxAttribute",
"-System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute")
.SetAssemblyFilters("+FluentAssertions"));
string link = RootDirectory / "TestResults" / "reports" / "index.html";
string link = TestResultsDirectory / "reports" / "index.html";
Serilog.Log.Information($"Code coverage report: \x1b]8;;file://{link.Replace('\\', '/')}\x1b\\{link}\x1b]8;;\x1b\\");
});

Expand All @@ -192,7 +199,10 @@ class Build : NukeBuild
.SetConfiguration("Debug")
.EnableNoBuild()
.SetDataCollector("XPlat Code Coverage")
.SetResultsDirectory(RootDirectory / "TestResults")
.SetResultsDirectory(TestResultsDirectory)
.AddRunSetting(
"DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.DoesNotReturnAttribute",
"DoesNotReturnAttribute")
.CombineWith(
testCombinations,
(_, v) => _.SetProjectFile(v.project).SetFramework(v.framework)));
Expand Down
2 changes: 2 additions & 0 deletions Src/FluentAssertions/AssertionExtensions.cs
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq.Expressions;
using System.Net.Http;
Expand Down Expand Up @@ -999,6 +1000,7 @@ public static void Should(this TypeSelectorAssertions _)
InvalidShouldCall();
}

[DoesNotReturn]
private static void InvalidShouldCall()
{
throw new InvalidOperationException("You are asserting the 'AndConstraint' itself. Remove the 'Should()' method directly following 'And'.");
Expand Down
18 changes: 18 additions & 0 deletions Src/FluentAssertions/DoesNotReturnAttribute.cs
@@ -0,0 +1,18 @@
// copied from https://source.dot.net/#System.Private.CoreLib/NullableAttributes.cs
#if !(NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Diagnostics.CodeAnalysis
{
/// <summary>Applied to a method that will never return under any circumstance.</summary>
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
#if SYSTEM_PRIVATE_CORELIB
public
#else
internal
#endif
sealed class DoesNotReturnAttribute : Attribute
{
}
}
#endif
3 changes: 3 additions & 0 deletions Src/FluentAssertions/Execution/FallbackTestFramework.cs
@@ -1,3 +1,5 @@
using System.Diagnostics.CodeAnalysis;

namespace FluentAssertions.Execution
{
/// <summary>
Expand All @@ -13,6 +15,7 @@ internal class FallbackTestFramework : ITestFramework
/// <summary>
/// Throws a framework-specific exception to indicate a failing unit test.
/// </summary>
[DoesNotReturn]
public void Throw(string message)
{
throw new AssertionFailedException(message);
Expand Down
5 changes: 4 additions & 1 deletion Src/FluentAssertions/Execution/ITestFramework.cs
@@ -1,4 +1,6 @@
namespace FluentAssertions.Execution
using System.Diagnostics.CodeAnalysis;

namespace FluentAssertions.Execution
{
/// <summary>
/// Represents an abstraction of a particular test framework such as MSTest, nUnit, etc.
Expand All @@ -13,6 +15,7 @@ internal interface ITestFramework
/// <summary>
/// Throws a framework-specific exception to indicate a failing unit test.
/// </summary>
[DoesNotReturn]
void Throw(string message);
}
}
2 changes: 2 additions & 0 deletions Src/FluentAssertions/Execution/LateBoundTestFramework.cs
@@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;

Expand All @@ -8,6 +9,7 @@ internal abstract class LateBoundTestFramework : ITestFramework
{
private Assembly assembly;

[DoesNotReturn]
public void Throw(string message)
{
Type exceptionType = assembly.GetType(ExceptionFullName);
Expand Down
2 changes: 2 additions & 0 deletions Src/FluentAssertions/Execution/NSpecFramework.cs
@@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;

Expand Down Expand Up @@ -27,6 +28,7 @@ public bool IsAvailable
}
}

[DoesNotReturn]
public void Throw(string message)
{
Type exceptionType = assembly.GetType("NSpec.Domain.AssertionException");
Expand Down
2 changes: 2 additions & 0 deletions Src/FluentAssertions/Execution/TestFrameworkProvider.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using FluentAssertions.Common;

Expand All @@ -22,6 +23,7 @@ internal static class TestFrameworkProvider

#endregion

[DoesNotReturn]
public static void Throw(string message)
{
if (testFramework is null)
Expand Down
2 changes: 2 additions & 0 deletions Src/FluentAssertions/Execution/XUnit2TestFramework.cs
@@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;

namespace FluentAssertions.Execution
Expand All @@ -25,6 +26,7 @@ public bool IsAvailable
}
}

[DoesNotReturn]
public void Throw(string message)
{
Type exceptionType = assembly.GetType("Xunit.Sdk.XunitException");
Expand Down
49 changes: 48 additions & 1 deletion Tests/FluentAssertions.Specs/AssertionExtensionsSpecs.cs
Expand Up @@ -2,6 +2,9 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using FluentAssertions.Numeric;
using FluentAssertions.Primitives;
using FluentAssertions.Specialized;
using FluentAssertions.Types;
using Xunit;

Expand Down Expand Up @@ -32,6 +35,49 @@ private static bool OverridesEquals(Type t)
return equals is not null;
}

[Theory]
[InlineData(typeof(ReferenceTypeAssertions<object, ObjectAssertions>))]
[InlineData(typeof(BooleanAssertions<BooleanAssertions>))]
[InlineData(typeof(DateTimeAssertions<DateTimeAssertions>))]
[InlineData(typeof(DateTimeOffsetAssertions<DateTimeOffsetAssertions>))]
#if NET6_0_OR_GREATER
[InlineData(typeof(DateOnlyAssertions<DateOnlyAssertions>))]
#endif
[InlineData(typeof(ExecutionTimeAssertions))]
[InlineData(typeof(GuidAssertions<GuidAssertions>))]
[InlineData(typeof(MethodInfoSelectorAssertions))]
[InlineData(typeof(NumericAssertions<int, NumericAssertions<int>>))]
[InlineData(typeof(PropertyInfoSelectorAssertions))]
[InlineData(typeof(SimpleTimeSpanAssertions<SimpleTimeSpanAssertions>))]
[InlineData(typeof(TaskCompletionSourceAssertions<int>))]
[InlineData(typeof(TypeSelectorAssertions))]
[InlineData(typeof(EnumAssertions<StringComparison, EnumAssertions<StringComparison>>))]
public void Fake_should_method_throws(Type type)
{
// Arrange
MethodInfo fakeOverload = AllTypes.From(typeof(FluentAssertions.AssertionExtensions).Assembly)
.ThatAreClasses()
.ThatAreStatic()
.Where(t => t.IsPublic)
.SelectMany(t => t.GetMethods(BindingFlags.Static | BindingFlags.Public))
.Single(m => m.Name == "Should" && IsGuardOverload(m)
&& m.GetParameters().Single().ParameterType.Name == type.Name);

if (type.IsConstructedGenericType)
{
fakeOverload = fakeOverload.MakeGenericMethod(type.GenericTypeArguments);
}

// Act
Action act = () => fakeOverload.Invoke(null, new object[] { null });

// Assert
act.Should()
.ThrowExactly<TargetInvocationException>()
.WithInnerExceptionExactly<InvalidOperationException>()
.WithMessage("You are asserting the 'AndConstraint' itself. Remove the 'Should()' method directly following 'And'.");
}

[Fact]
public void Should_methods_have_a_matching_overload_to_guard_against_chaining_and_constraints()
{
Expand All @@ -58,7 +104,8 @@ public void Should_methods_have_a_matching_overload_to_guard_against_chaining_an
// Assert
fakeOverloads.Should().BeEquivalentTo(realOverloads, opt => opt
.Using<Type>(ctx => ctx.Subject.Name.Should().Be(ctx.Expectation.Name))
.WhenTypeIs<Type>());
.WhenTypeIs<Type>(),
"AssertionExtensions.cs should have a guard overload of Should calling InvalidShouldCall()");
}

private static bool IsGuardOverload(MethodInfo m) =>
Expand Down

0 comments on commit 6a4ee57

Please sign in to comment.