Skip to content

Commit 94e1c8b

Browse files
authored
feat: support nunit assert.that migration (#335)
* feat: support nunit assert.that migration
1 parent 5cf9e97 commit 94e1c8b

File tree

5 files changed

+170
-41
lines changed

5 files changed

+170
-41
lines changed

docs/NunitAnalyzer.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ var flag = true;
2323
// old assertion:
2424
Assert.IsTrue(flag);
2525
Assert.True(flag);
26+
Assert.That(flag);
27+
Assert.That(flag, Is.True);
28+
Assert.That(flag, Is.Not.False);
2629

2730
// new assertion:
2831
flag.Should().BeTrue();
@@ -40,6 +43,15 @@ Assert.True(flag); /* fail message: Expected: True
4043
Assert.IsTrue(flag); /* fail message: Expected: True
4144
But was: False
4245
*/
46+
Assert.That(flag); /* fail message: Expected: True
47+
But was: False
48+
*/
49+
Assert.That(flag, Is.True); /* fail message: Expected: True
50+
But was: False
51+
*/
52+
Assert.That(flag, Is.Not.False); /* fail message: Expected: not False
53+
But was: False
54+
*/
4355

4456
// new assertion:
4557
flag.Should().BeTrue(); /* fail message: Expected flag to be true, but found False. */
@@ -54,6 +66,8 @@ var flag = false;
5466
// old assertion:
5567
Assert.IsFalse(flag);
5668
Assert.False(flag);
69+
Assert.That(flag, Is.False);
70+
Assert.That(flag, Is.Not.True);
5771

5872
// new assertion:
5973
flag.Should().BeFalse();
@@ -71,6 +85,12 @@ Assert.False(flag); /* fail message: Expected: False
7185
Assert.IsFalse(flag); /* fail message: Expected: False
7286
But was: True
7387
*/
88+
Assert.That(flag, Is.False); /* fail message: Expected: False
89+
But was: True
90+
*/
91+
Assert.That(flag, Is.Not.True); /* fail message: Expected: not True
92+
But was: True
93+
*/
7494

7595
// new assertion:
7696
flag.Should().BeFalse(); /* fail message: Expected flag to be false, but found True. */

src/FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs/NunitAnalyzerTests.cs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
using Microsoft.VisualStudio.TestTools.UnitTesting;
22
using Assert = NUnit.Framework.Assert;
3-
using FluentAssertions;
43
using System.Collections.Generic;
4+
using NUnit.Framework;
5+
using FluentAssertions;
56

67
namespace FluentAssertions.Analyzers.FluentAssertionAnalyzerDocs;
78

@@ -17,6 +18,9 @@ public void BooleanAssertIsTrue()
1718
// old assertion:
1819
Assert.IsTrue(flag);
1920
Assert.True(flag);
21+
Assert.That(flag);
22+
Assert.That(flag, Is.True);
23+
Assert.That(flag, Is.Not.False);
2024

2125
// new assertion:
2226
flag.Should().BeTrue();
@@ -42,6 +46,36 @@ public void BooleanAssertIsTrue_Failure_OldAssertion_1()
4246
Assert.IsTrue(flag);
4347
}
4448

49+
[TestMethod, ExpectedTestFrameworkException]
50+
public void BooleanAssertIsTrue_Failure_OldAssertion_2()
51+
{
52+
// arrange
53+
var flag = false;
54+
55+
// old assertion:
56+
Assert.That(flag);
57+
}
58+
59+
[TestMethod, ExpectedTestFrameworkException]
60+
public void BooleanAssertIsTrue_Failure_OldAssertion_3()
61+
{
62+
// arrange
63+
var flag = false;
64+
65+
// old assertion:
66+
Assert.That(flag, Is.True);
67+
}
68+
69+
[TestMethod, ExpectedTestFrameworkException]
70+
public void BooleanAssertIsTrue_Failure_OldAssertion_4()
71+
{
72+
// arrange
73+
var flag = false;
74+
75+
// old assertion:
76+
Assert.That(flag, Is.Not.False);
77+
}
78+
4579
[TestMethod, ExpectedTestFrameworkException]
4680
public void BooleanAssertIsTrue_Failure_NewAssertion()
4781
{
@@ -61,6 +95,8 @@ public void BooleanAssertIsFalse()
6195
// old assertion:
6296
Assert.IsFalse(flag);
6397
Assert.False(flag);
98+
Assert.That(flag, Is.False);
99+
Assert.That(flag, Is.Not.True);
64100

65101
// new assertion:
66102
flag.Should().BeFalse();
@@ -86,6 +122,26 @@ public void BooleanAssertIsFalse_Failure_OldAssertion_1()
86122
Assert.IsFalse(flag);
87123
}
88124

125+
[TestMethod, ExpectedTestFrameworkException]
126+
public void BooleanAssertIsFalse_Failure_OldAssertion_2()
127+
{
128+
// arrange
129+
var flag = true;
130+
131+
// old assertion:
132+
Assert.That(flag, Is.False);
133+
}
134+
135+
[TestMethod, ExpectedTestFrameworkException]
136+
public void BooleanAssertIsFalse_Failure_OldAssertion_3()
137+
{
138+
// arrange
139+
var flag = true;
140+
141+
// old assertion:
142+
Assert.That(flag, Is.Not.True);
143+
}
144+
89145
[TestMethod, ExpectedTestFrameworkException]
90146
public void BooleanAssertIsFalse_Failure_NewAssertion()
91147
{

src/FluentAssertions.Analyzers.Tests/Tips/NunitTests.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ public class NunitTests
1515
[AssertionDiagnostic("Assert.True(bool.Parse(\"true\"){0});")]
1616
[AssertionDiagnostic("Assert.IsTrue(actual{0});")]
1717
[AssertionDiagnostic("Assert.IsTrue(bool.Parse(\"true\"){0});")]
18+
[AssertionDiagnostic("Assert.That(actual{0});")]
19+
[AssertionDiagnostic("Assert.That(actual, Is.True{0});")]
20+
[AssertionDiagnostic("Assert.That(actual, Is.Not.False{0});")]
1821
[Implemented]
1922
public void Nunit3_AssertTrue_TestAnalyzer(string assertion) => Nunit3VerifyDiagnostic("bool actual", assertion);
2023

@@ -51,6 +54,15 @@ public class NunitTests
5154
[AssertionCodeFix(
5255
oldAssertion: "Assert.IsTrue(actual == false{0});",
5356
newAssertion: "(actual == false).Should().BeTrue({0});")]
57+
[AssertionCodeFix(
58+
oldAssertion: "Assert.That(actual{0});",
59+
newAssertion: "actual.Should().BeTrue({0});")]
60+
[AssertionCodeFix(
61+
oldAssertion: "Assert.That(actual, Is.True{0});",
62+
newAssertion: "actual.Should().BeTrue({0});")]
63+
[AssertionCodeFix(
64+
oldAssertion: "Assert.That(actual, Is.Not.False{0});",
65+
newAssertion: "actual.Should().BeTrue({0});")]
5466
[Implemented]
5567
public void Nunit3_AssertTrue_TestCodeFix(string oldAssertion, string newAssertion) => Nunit3VerifyFix("bool actual", oldAssertion, newAssertion);
5668

src/FluentAssertions.Analyzers/Tips/NunitCodeFixProvider.cs

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@
1111
namespace FluentAssertions.Analyzers;
1212

1313
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(NunitCodeFixProvider)), Shared]
14-
public class NunitCodeFixProvider : TestingFrameworkCodeFixProvider
14+
public class NunitCodeFixProvider : TestingFrameworkCodeFixProvider<NunitCodeFixProvider.NunitCodeFixContext>
1515
{
1616
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(AssertAnalyzer.NUnitRule.Id);
17-
protected override CreateChangedDocument TryComputeFixCore(IInvocationOperation invocation, CodeFixContext context, TestingFrameworkCodeFixContext t, Diagnostic diagnostic)
17+
protected override NunitCodeFixContext CreateTestContext(SemanticModel semanticModel) => new(semanticModel.Compilation);
18+
protected override CreateChangedDocument TryComputeFixCore(IInvocationOperation invocation, CodeFixContext context, NunitCodeFixContext t, Diagnostic diagnostic)
1819
{
1920
var assertType = invocation.TargetMethod.ContainingType;
2021
var nunitVersion = assertType.ContainingAssembly.Identity.Version;
@@ -24,6 +25,7 @@ protected override CreateChangedDocument TryComputeFixCore(IInvocationOperation
2425

2526
return assertType.Name switch
2627
{
28+
"Assert" when invocation.TargetMethod.Name is "That" => TryComputeFixForNunitThat(invocation, context, t),
2729
"Assert" when isNunit3 => TryComputeFixForNunitClassicAssert(invocation, context, t),
2830
"ClassicAssert" when isNunit4 => TryComputeFixForNunitClassicAssert(invocation, context, t),
2931
//"StringAssert" => TryComputeFixForStringAssert(invocation, context, testContext),
@@ -32,7 +34,7 @@ protected override CreateChangedDocument TryComputeFixCore(IInvocationOperation
3234
};
3335
}
3436

35-
private CreateChangedDocument TryComputeFixForNunitClassicAssert(IInvocationOperation invocation, CodeFixContext context, TestingFrameworkCodeFixContext t)
37+
private CreateChangedDocument TryComputeFixForNunitClassicAssert(IInvocationOperation invocation, CodeFixContext context, NunitCodeFixContext t)
3638
{
3739
switch (invocation.TargetMethod.Name)
3840
{
@@ -228,4 +230,41 @@ private CreateChangedDocument TryComputeFixForNunitClassicAssert(IInvocationOper
228230
}
229231
return null;
230232
}
233+
234+
private CreateChangedDocument TryComputeFixForNunitThat(IInvocationOperation invocation, CodeFixContext context, NunitCodeFixContext t)
235+
{
236+
if (invocation.Arguments.Length is 1 && invocation.Arguments[0].Value.Type.EqualsSymbol(t.Boolean) // Assert.That(subject)
237+
|| invocation.Arguments.Length > 2 && invocation.Arguments[0].Value.Type.EqualsSymbol(t.Boolean) && invocation.Arguments[1].Value.Type.EqualsSymbol(t.String)) // Assert.That(subject, message)
238+
{
239+
return DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, "BeTrue", subjectIndex: 0, argumentsToRemove: []);
240+
}
241+
242+
if (invocation.Arguments[1].Value.UnwrapConversion() is not IPropertyReferenceOperation constraint) return null;
243+
244+
switch (constraint.Property.Name)
245+
{
246+
case "True" when constraint.Property.ContainingType.EqualsSymbol(t.Is): // Assert.That(subject, Is.True)
247+
case "False" when constraint.Instance is IPropertyReferenceOperation { Property.Name: "Not" } chainedReference && PropertyReferencedFromType(chainedReference, t.Is): // Assert.That(subject, Is.Not.False)
248+
return DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, "BeTrue", subjectIndex: 0, argumentsToRemove: [1]);
249+
case "True" when constraint.Instance is IPropertyReferenceOperation { Property.Name: "Not" } chainedReference && PropertyReferencedFromType(chainedReference, t.Is): // Assert.That(subject, Is.Not.True)
250+
case "False" when PropertyReferencedFromType(constraint, t.Is): // Assert.That(subject, Is.False)
251+
return DocumentEditorUtils.RenameMethodToSubjectShouldAssertion(invocation, context, "BeFalse", subjectIndex: 0, argumentsToRemove: [1]);
252+
253+
default:
254+
return null;
255+
}
256+
257+
}
258+
259+
private static bool PropertyReferencedFromType(IPropertyReferenceOperation propertyReference, INamedTypeSymbol type) => propertyReference.Property.ContainingType.EqualsSymbol(type);
260+
261+
public class NunitCodeFixContext(Compilation compilation) : TestingFrameworkCodeFixProvider.TestingFrameworkCodeFixContext(compilation)
262+
{
263+
public INamedTypeSymbol Is { get; } = compilation.GetTypeByMetadataName("NUnit.Framework.Is");
264+
public INamedTypeSymbol Has { get; } = compilation.GetTypeByMetadataName("NUnit.Framework.Has");
265+
public INamedTypeSymbol Does { get; } = compilation.GetTypeByMetadataName("NUnit.Framework.Does");
266+
public INamedTypeSymbol Contains { get; } = compilation.GetTypeByMetadataName("NUnit.Framework.Contains");
267+
public INamedTypeSymbol Throws { get; } = compilation.GetTypeByMetadataName("NUnit.Framework.Throws");
268+
public INamedTypeSymbol ConstraintExpression { get; } = compilation.GetTypeByMetadataName("NUnit.Framework.Constraints.ConstraintExpression");
269+
}
231270
}

src/FluentAssertions.Analyzers/Tips/TestingFrameworkCodeFixProvider.cs

Lines changed: 39 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,49 @@
77
using Microsoft.CodeAnalysis.CodeFixes;
88
using Microsoft.CodeAnalysis.CSharp;
99
using Microsoft.CodeAnalysis.CSharp.Syntax;
10-
using Microsoft.CodeAnalysis.Editing;
1110
using Microsoft.CodeAnalysis.Formatting;
1211
using Microsoft.CodeAnalysis.Operations;
13-
using Microsoft.CodeAnalysis.Options;
1412
using Microsoft.CodeAnalysis.Simplification;
1513

1614
namespace FluentAssertions.Analyzers;
1715

18-
public abstract class TestingFrameworkCodeFixProvider : CodeFixProviderBase<TestingFrameworkCodeFixProvider.TestingFrameworkCodeFixContext>
16+
public abstract class TestingFrameworkCodeFixProvider<TTestContext> : CodeFixProviderBase<TTestContext> where TTestContext : TestingFrameworkCodeFixProvider.TestingFrameworkCodeFixContext
1917
{
2018
protected override string Title => "Replace with FluentAssertions";
2119

22-
protected override TestingFrameworkCodeFixContext CreateTestContext(SemanticModel semanticModel) => new TestingFrameworkCodeFixContext(semanticModel.Compilation);
20+
protected override Func<CancellationToken, Task<Document>> TryComputeFix(IInvocationOperation invocation, CodeFixContext context, TTestContext t, Diagnostic diagnostic)
21+
{
22+
var fix = TryComputeFixCore(invocation, context, t, diagnostic);
23+
if (fix is null)
24+
{
25+
return null;
26+
}
27+
28+
return async ctx =>
29+
{
30+
const string fluentAssertionNamespace = "FluentAssertions";
31+
var document = await fix(ctx);
32+
33+
var model = await document.GetSemanticModelAsync();
34+
var scopes = model.GetImportScopes(diagnostic.Location.SourceSpan.Start);
35+
36+
var hasFluentAssertionImport = scopes.Any(scope => scope.Imports.Any(import => import.NamespaceOrType.ToString().Equals(fluentAssertionNamespace)));
37+
if (hasFluentAssertionImport)
38+
{
39+
return document;
40+
}
41+
42+
var root = (CompilationUnitSyntax)await document.GetSyntaxRootAsync();
43+
root = root.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(fluentAssertionNamespace)).WithAdditionalAnnotations(Simplifier.AddImportsAnnotation));
44+
45+
document = document.WithSyntaxRoot(root);
46+
document = await Formatter.OrganizeImportsAsync(document);
47+
48+
return document;
49+
};
50+
}
51+
52+
protected abstract Func<CancellationToken, Task<Document>> TryComputeFixCore(IInvocationOperation invocation, CodeFixContext context, TTestContext t, Diagnostic diagnostic);
2353

2454
protected static bool ArgumentsAreTypeOf(IInvocationOperation invocation, params ITypeSymbol[] types) => ArgumentsAreTypeOf(invocation, 0, types);
2555
protected static bool ArgumentsAreTypeOf(IInvocationOperation invocation, int startFromIndex, params ITypeSymbol[] types)
@@ -82,42 +112,14 @@ protected static bool ArgumentsCount(IInvocationOperation invocation, int argume
82112
return invocation.TargetMethod.Parameters.Length == arguments;
83113
}
84114

85-
protected override Func<CancellationToken, Task<Document>> TryComputeFix(IInvocationOperation invocation, CodeFixContext context, TestingFrameworkCodeFixContext t, Diagnostic diagnostic)
86-
{
87-
var fix = TryComputeFixCore(invocation, context, t, diagnostic);
88-
if (fix is null)
89-
{
90-
return null;
91-
}
92-
93-
return async ctx =>
94-
{
95-
const string fluentAssertionNamespace = "FluentAssertions";
96-
var document = await fix(ctx);
97-
98-
var model = await document.GetSemanticModelAsync();
99-
var scopes = model.GetImportScopes(diagnostic.Location.SourceSpan.Start);
100-
101-
var hasFluentAssertionImport = scopes.Any(scope => scope.Imports.Any(import => import.NamespaceOrType.ToString().Equals(fluentAssertionNamespace)));
102-
if (hasFluentAssertionImport)
103-
{
104-
return document;
105-
}
106-
107-
var root = (CompilationUnitSyntax) await document.GetSyntaxRootAsync();
108-
root = root.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(fluentAssertionNamespace)).WithAdditionalAnnotations(Simplifier.AddImportsAnnotation));
115+
}
109116

110-
document = document.WithSyntaxRoot(root);
111-
document = await Formatter.OrganizeImportsAsync(document);
112-
113-
return document;
114-
};
115-
}
116-
117-
protected abstract Func<CancellationToken, Task<Document>> TryComputeFixCore(IInvocationOperation invocation, CodeFixContext context, TestingFrameworkCodeFixContext t, Diagnostic diagnostic);
117+
public abstract class TestingFrameworkCodeFixProvider : TestingFrameworkCodeFixProvider<TestingFrameworkCodeFixProvider.TestingFrameworkCodeFixContext>
118+
{
119+
protected override TestingFrameworkCodeFixContext CreateTestContext(SemanticModel semanticModel) => new(semanticModel.Compilation);
118120

119121

120-
public sealed class TestingFrameworkCodeFixContext(Compilation compilation)
122+
public class TestingFrameworkCodeFixContext(Compilation compilation)
121123
{
122124
public INamedTypeSymbol Object { get; } = compilation.ObjectType;
123125
public INamedTypeSymbol String { get; } = compilation.GetTypeByMetadataName("System.String");

0 commit comments

Comments
 (0)