Skip to content

Commit b19982c

Browse files
authored
feature: improve numeric assertions (#275)
* feature: improve numeric assertions * add test NumericShouldBeInRange_BeLessOrEqualToAndBeGreaterOrEqualTo_WithMessagesInBothAssertions_TestAnalyzer
1 parent f4adf27 commit b19982c

File tree

4 files changed

+77
-27
lines changed

4 files changed

+77
-27
lines changed

Diff for: src/FluentAssertions.Analyzers.TestUtils/GenerateCode.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,17 @@ public static string GenericIListExpressionBodyAssertion(string assertion) => Ge
9898
.AppendLine("}")
9999
.ToString();
100100

101-
public static string DoubleAssertion(string assertion) => new StringBuilder()
101+
public static string DoubleAssertion(string assertion) => NumericAssertion(assertion, "double");
102+
103+
public static string NumericAssertion(string assertion, string type) => new StringBuilder()
102104
.AppendLine("using System;")
103105
.AppendLine("using FluentAssertions;")
104106
.AppendLine("using FluentAssertions.Extensions;")
105107
.AppendLine("namespace TestNamespace")
106108
.AppendLine("{")
107109
.AppendLine(" class TestClass")
108110
.AppendLine(" {")
109-
.AppendLine(" void TestMethod(double actual, double expected, double lower, double upper, double delta)")
111+
.AppendLine($" void TestMethod({type} actual, {type} expected, {type} lower, {type} upper, {type} delta)")
110112
.AppendLine(" {")
111113
.AppendLine($" {assertion}")
112114
.AppendLine(" }")

Diff for: src/FluentAssertions.Analyzers.Tests/Tips/NumericTests.cs

+65-21
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public class NumericTests
2121
oldAssertion: "actual.Should().BeGreaterThan(0{0}).ToString();",
2222
newAssertion: "actual.Should().BePositive({0}).ToString();")]
2323
[Implemented]
24-
public void NumericShouldBePositive_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix<FluentAssertionsCodeFix, FluentAssertionsOperationAnalyzer>(oldAssertion, newAssertion);
24+
public void NumericShouldBePositive_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix(oldAssertion, newAssertion);
2525

2626
[DataTestMethod]
2727
[AssertionDiagnostic("actual.Should().BeLessThan(0{0});")]
@@ -37,7 +37,7 @@ public class NumericTests
3737
oldAssertion: "actual.Should().BeLessThan(0{0}).ToString();",
3838
newAssertion: "actual.Should().BeNegative({0}).ToString();")]
3939
[Implemented]
40-
public void NumericShouldBeNegative_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix<FluentAssertionsCodeFix, FluentAssertionsOperationAnalyzer>(oldAssertion, newAssertion);
40+
public void NumericShouldBeNegative_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix(oldAssertion, newAssertion);
4141

4242
[DataTestMethod]
4343
[AssertionDiagnostic("actual.Should().BeGreaterOrEqualTo(lower{0}).And.BeLessOrEqualTo(upper);")]
@@ -51,6 +51,27 @@ public class NumericTests
5151
[Implemented]
5252
public void NumericShouldBeInRange_BeLessOrEqualToAndBeGreaterOrEqualTo_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic(assertion, DiagnosticMetadata.NumericShouldBeInRange_BeLessOrEqualToAndBeGreaterOrEqualTo);
5353

54+
[DataTestMethod]
55+
[DataRow("actual.Should().BeLessOrEqualTo(upper, \"because reason 1\").And.BeGreaterOrEqualTo(lower, \"because reason 2\");")]
56+
[DataRow("actual.Should().BeLessOrEqualTo(upper, \"because reason 1\").And.BeGreaterOrEqualTo(lower, \"because reason 2\");")]
57+
[Implemented]
58+
public void NumericShouldBeInRange_BeLessOrEqualToAndBeGreaterOrEqualTo_WithMessagesInBothAssertions_TestAnalyzer(string assertion)
59+
{
60+
verifyNoDiagnostic("double");
61+
verifyNoDiagnostic("float");
62+
verifyNoDiagnostic("decimal");
63+
64+
void verifyNoDiagnostic(string type)
65+
{
66+
var source = GenerateCode.NumericAssertion(assertion, type);
67+
DiagnosticVerifier.VerifyDiagnostic(new DiagnosticVerifierArguments()
68+
.WithSources(source)
69+
.WithAllAnalyzers()
70+
.WithPackageReferences(PackageReference.FluentAssertions_6_12_0)
71+
);
72+
}
73+
}
74+
5475
[DataTestMethod]
5576
[AssertionCodeFix(
5677
oldAssertion: "actual.Should().BeGreaterOrEqualTo(lower{0}).And.BeLessOrEqualTo(upper);",
@@ -65,7 +86,7 @@ public class NumericTests
6586
oldAssertion: "actual.Should().BeLessOrEqualTo(upper).And.BeGreaterOrEqualTo(lower{0});",
6687
newAssertion: "actual.Should().BeInRange(lower, upper{0});")]
6788
[NotImplemented]
68-
public void NumericShouldBeInRange_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix<FluentAssertionsCodeFix, FluentAssertionsOperationAnalyzer>(oldAssertion, newAssertion);
89+
public void NumericShouldBeInRange_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix(oldAssertion, newAssertion);
6990

7091
[DataTestMethod]
7192
[AssertionDiagnostic("Math.Abs(expected - actual).Should().BeLessOrEqualTo(delta{0});")]
@@ -77,33 +98,56 @@ public class NumericTests
7798
oldAssertion: "Math.Abs(expected - actual).Should().BeLessOrEqualTo(delta{0});",
7899
newAssertion: "actual.Should().BeApproximately(expected, delta{0});")]
79100
[Implemented]
80-
public void NumericShouldBeApproximately_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix<FluentAssertionsCodeFix, FluentAssertionsOperationAnalyzer>(oldAssertion, newAssertion);
101+
public void NumericShouldBeApproximately_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix(oldAssertion, newAssertion);
81102

82103
private void VerifyCSharpDiagnostic(string sourceAssertion, DiagnosticMetadata metadata)
83104
{
84-
var source = GenerateCode.DoubleAssertion(sourceAssertion);
105+
VerifyCSharpDiagnostic(sourceAssertion, metadata, "double");
106+
VerifyCSharpDiagnostic(sourceAssertion, metadata, "float");
107+
VerifyCSharpDiagnostic(sourceAssertion, metadata, "decimal");
108+
}
85109

86-
DiagnosticVerifier.VerifyCSharpDiagnosticUsingAllAnalyzers(source, new DiagnosticResult
87-
{
88-
Id = FluentAssertionsOperationAnalyzer.DiagnosticId,
89-
Message = metadata.Message,
90-
VisitorName = metadata.Name,
91-
Locations = new DiagnosticResultLocation[]
110+
private void VerifyCSharpDiagnostic(string sourceAssertion, DiagnosticMetadata metadata, string numericType)
111+
{
112+
var source = GenerateCode.NumericAssertion(sourceAssertion, numericType);
113+
114+
DiagnosticVerifier.VerifyDiagnostic(new DiagnosticVerifierArguments()
115+
.WithSources(source)
116+
.WithAllAnalyzers()
117+
.WithPackageReferences(PackageReference.FluentAssertions_6_12_0)
118+
.WithExpectedDiagnostics(new DiagnosticResult
92119
{
93-
new DiagnosticResultLocation("Test0.cs", 10, 13)
94-
},
95-
Severity = DiagnosticSeverity.Info
96-
});
120+
Id = FluentAssertionsOperationAnalyzer.DiagnosticId,
121+
Message = metadata.Message,
122+
VisitorName = metadata.Name,
123+
Locations = new DiagnosticResultLocation[]
124+
{
125+
new DiagnosticResultLocation("Test0.cs", 10, 13)
126+
},
127+
Severity = DiagnosticSeverity.Info
128+
})
129+
);
130+
}
131+
132+
private void VerifyCSharpFix(string oldSourceAssertion, string newSourceAssertion)
133+
{
134+
VerifyCSharpFix(oldSourceAssertion, newSourceAssertion, "double");
135+
VerifyCSharpFix(oldSourceAssertion, newSourceAssertion, "float");
136+
VerifyCSharpFix(oldSourceAssertion, newSourceAssertion, "decimal");
97137
}
98138

99-
private void VerifyCSharpFix<TCodeFixProvider, TDiagnosticAnalyzer>(string oldSourceAssertion, string newSourceAssertion)
100-
where TCodeFixProvider : Microsoft.CodeAnalysis.CodeFixes.CodeFixProvider, new()
101-
where TDiagnosticAnalyzer : Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer, new()
139+
private void VerifyCSharpFix(string oldSourceAssertion, string newSourceAssertion, string numericType)
102140
{
103-
var oldSource = GenerateCode.DoubleAssertion(oldSourceAssertion);
104-
var newSource = GenerateCode.DoubleAssertion(newSourceAssertion);
141+
var oldSource = GenerateCode.NumericAssertion(oldSourceAssertion, numericType);
142+
var newSource = GenerateCode.NumericAssertion(newSourceAssertion, numericType);
105143

106-
DiagnosticVerifier.VerifyCSharpFix<TCodeFixProvider, TDiagnosticAnalyzer>(oldSource, newSource);
144+
DiagnosticVerifier.VerifyFix(new CodeFixVerifierArguments()
145+
.WithCodeFixProvider<FluentAssertionsCodeFix>()
146+
.WithDiagnosticAnalyzer<FluentAssertionsOperationAnalyzer>()
147+
.WithSources(oldSource)
148+
.WithFixedSources(newSource)
149+
.WithPackageReferences(PackageReference.FluentAssertions_6_12_0)
150+
);
107151
}
108152
}
109153
}

Diff for: src/FluentAssertions.Analyzers/Tips/FluentAssertionsOperationAnalyzer.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,11 @@ private static void AnalyzeInvocation(OperationAnalysisContext context, FluentAs
445445
case nameof(Enumerable.Count) when IsEnumerableMethodWithoutArguments(invocationBeforeShould, metadata):
446446
context.ReportDiagnostic(CreateDiagnostic(assertion, DiagnosticMetadata.CollectionShouldHaveCountLessOrEqualTo_CountShouldBeLessOrEqualTo));
447447
return;
448-
case nameof(Math.Abs) when invocationBeforeShould.IsContainedInType(metadata.Math) && assertion.Arguments[0].IsReferenceOfType<double>():
448+
case nameof(Math.Abs) when invocationBeforeShould.IsContainedInType(metadata.Math) && (
449+
assertion.Arguments[0].IsReferenceOfType(SpecialType.System_Double)
450+
|| assertion.Arguments[0].IsReferenceOfType(SpecialType.System_Single)
451+
|| assertion.Arguments[0].IsReferenceOfType(SpecialType.System_Decimal)
452+
):
449453
context.ReportDiagnostic(CreateDiagnostic(assertion, DiagnosticMetadata.NumericShouldBeApproximately_MathAbsShouldBeLessOrEqualTo));
450454
return;
451455
}

Diff for: src/FluentAssertions.Analyzers/Utilities/OperartionExtensions.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,13 @@ public static bool IsReference(this IArgumentOperation argument)
9595
return operation.Kind is OperationKind.LocalReference || operation.Kind is OperationKind.ParameterReference;
9696
}
9797

98-
public static bool IsReferenceOfType<T>(this IArgumentOperation argument)
98+
public static bool IsReferenceOfType(this IArgumentOperation argument, SpecialType type)
9999
{
100100
var current = UnwrapConversion(argument.Value);
101101
return current switch
102102
{
103-
ILocalReferenceOperation local => local.Local.Type.SpecialType == SpecialType.System_Double,
104-
IParameterReferenceOperation parameter => parameter.Parameter.Type.SpecialType == SpecialType.System_Double,
103+
ILocalReferenceOperation local => local.Local.Type.SpecialType == type,
104+
IParameterReferenceOperation parameter => parameter.Parameter.Type.SpecialType == type,
105105
_ => false,
106106
};
107107
}

0 commit comments

Comments
 (0)