diff --git a/TUnit.Analyzers.CodeFixers/Base/AssertionRewriter.cs b/TUnit.Analyzers.CodeFixers/Base/AssertionRewriter.cs
index a5ef3fa98d..671ccf5c0f 100644
--- a/TUnit.Analyzers.CodeFixers/Base/AssertionRewriter.cs
+++ b/TUnit.Analyzers.CodeFixers/Base/AssertionRewriter.cs
@@ -41,7 +41,22 @@ protected AssertionRewriter(SemanticModel semanticModel)
public override SyntaxNode? VisitInvocationExpression(InvocationExpressionSyntax node)
{
- var convertedAssertion = ConvertAssertionIfNeeded(node);
+ // Wrap the conversion in try-catch to ensure one failing assertion doesn't break
+ // the conversion of all other assertions in the file
+ ExpressionSyntax? convertedAssertion;
+ try
+ {
+ convertedAssertion = ConvertAssertionIfNeeded(node);
+ }
+ catch (Exception ex) when (ex is InvalidOperationException or ArgumentException or NotSupportedException)
+ {
+ // If conversion fails for this specific assertion due to expected issues
+ // (e.g., invalid syntax, unsupported patterns), skip it and continue.
+ // This ensures partial conversion is better than no conversion.
+ // Unexpected exceptions will propagate for debugging.
+ return base.VisitInvocationExpression(node);
+ }
+
if (convertedAssertion != null)
{
var conversionTrivia = convertedAssertion.GetLeadingTrivia();
@@ -89,12 +104,88 @@ protected ExpressionSyntax CreateTUnitAssertion(
return CreateTUnitAssertionWithMessage(methodName, actualValue, null, additionalArguments);
}
+ ///
+ /// Creates a TUnit collection assertion for enumerable/collection types.
+ ///
+ ///
+ /// Note: We intentionally do NOT cast to IEnumerable<T> because:
+ /// 1. TUnit's Assert.That<T> overloads generally resolve correctly for arrays and collections
+ /// 2. Adding explicit casts creates noisy code that users would need to clean up
+ /// 3. If there's genuine overload ambiguity, users can add the cast manually
+ ///
+ protected ExpressionSyntax CreateTUnitCollectionAssertion(
+ string methodName,
+ ExpressionSyntax collectionValue,
+ params ArgumentSyntax[] additionalArguments)
+ {
+ return CreateTUnitAssertionWithMessage(methodName, collectionValue, null, additionalArguments);
+ }
+
+ ///
+ /// Ensures that ValueTask and Task types are properly awaited before being passed to Assert.That().
+ /// This is needed because TUnit's analyzer (TUnitAssertions0008) requires ValueTask to be awaited.
+ /// If the expression is already an await expression, it's returned as-is.
+ ///
+ private ExpressionSyntax EnsureTaskTypesAreAwaited(ExpressionSyntax expression)
+ {
+ // If already an await expression, no action needed
+ if (expression is AwaitExpressionSyntax)
+ {
+ return expression;
+ }
+
+ // Wrap semantic analysis in try-catch to handle TFM-specific failures
+ // This prevents AggregateException crashes in multi-target projects
+ try
+ {
+ // Try to get the type of the expression using semantic analysis
+ var typeInfo = SemanticModel.GetTypeInfo(expression);
+ if (typeInfo.Type is null || typeInfo.Type.TypeKind == TypeKind.Error)
+ {
+ return expression;
+ }
+
+ // Check if the type is ValueTask, ValueTask, Task, or Task
+ var typeName = typeInfo.Type.ToDisplayString();
+ var isTaskType = typeName.StartsWith("System.Threading.Tasks.ValueTask") ||
+ typeName.StartsWith("System.Threading.Tasks.Task") ||
+ typeName == "System.Threading.Tasks.ValueTask" ||
+ typeName == "System.Threading.Tasks.Task";
+
+ // Also check for the short names (when using directive is present)
+ if (!isTaskType && typeInfo.Type is INamedTypeSymbol namedType)
+ {
+ isTaskType = namedType.Name is "ValueTask" or "Task" &&
+ namedType.ContainingNamespace?.ToDisplayString() == "System.Threading.Tasks";
+ }
+
+ if (!isTaskType)
+ {
+ return expression;
+ }
+
+ // Wrap the expression in an await
+ var awaitKeyword = SyntaxFactory.Token(SyntaxKind.AwaitKeyword)
+ .WithTrailingTrivia(SyntaxFactory.Space);
+ return SyntaxFactory.AwaitExpression(awaitKeyword, expression);
+ }
+ catch (Exception ex) when (ex is InvalidOperationException or ArgumentException)
+ {
+ // Semantic analysis can fail in some TFM configurations (e.g., type not available
+ // in one target framework). Return expression unchanged and let the user handle it.
+ return expression;
+ }
+ }
+
protected ExpressionSyntax CreateTUnitAssertionWithMessage(
string methodName,
ExpressionSyntax actualValue,
ExpressionSyntax? message,
params ArgumentSyntax[] additionalArguments)
{
+ // Ensure ValueTask/Task types are properly awaited before passing to Assert.That
+ actualValue = EnsureTaskTypesAreAwaited(actualValue);
+
// Create Assert.That(actualValue)
var assertThatInvocation = SyntaxFactory.InvocationExpression(
SyntaxFactory.MemberAccessExpression(
@@ -154,6 +245,9 @@ protected ExpressionSyntax CreateTUnitGenericAssertion(
TypeSyntax typeArg,
ExpressionSyntax? message)
{
+ // Ensure ValueTask/Task types are properly awaited before passing to Assert.That
+ actualValue = EnsureTaskTypesAreAwaited(actualValue);
+
// Create Assert.That(actualValue)
var assertThatInvocation = SyntaxFactory.InvocationExpression(
SyntaxFactory.MemberAccessExpression(
@@ -414,24 +508,40 @@ protected static SyntaxTrivia CreateTodoComment(string message)
///
/// Determines if an invocation is a framework assertion method.
- /// Uses semantic analysis when available, with syntax-based fallback for resilience across TFMs.
+ /// IMPORTANT: Prioritizes syntax-based detection for deterministic results across TFMs.
+ /// This prevents AggregateException crashes in multi-target projects where semantic
+ /// analysis could produce different results for each target framework.
///
protected bool IsFrameworkAssertion(InvocationExpressionSyntax invocation)
{
- // Try semantic analysis first
- var symbolInfo = SemanticModel.GetSymbolInfo(invocation);
- if (symbolInfo.Symbol is IMethodSymbol methodSymbol)
+ // FIRST: Try syntax-based detection (deterministic across TFMs)
+ // This ensures consistent behavior for multi-target projects
+ if (IsFrameworkAssertionBySyntax(invocation))
{
- var namespaceName = methodSymbol.ContainingNamespace?.ToDisplayString() ?? "";
- if (IsFrameworkAssertionNamespace(namespaceName))
+ return true;
+ }
+
+ // SECOND: Fall back to semantic analysis for cases where syntax detection fails
+ // (e.g., aliased Assert types, extension methods, etc.)
+ try
+ {
+ var symbolInfo = SemanticModel.GetSymbolInfo(invocation);
+ if (symbolInfo.Symbol is IMethodSymbol methodSymbol)
{
- return true;
+ var namespaceName = methodSymbol.ContainingNamespace?.ToDisplayString() ?? "";
+ if (IsFrameworkAssertionNamespace(namespaceName))
+ {
+ return true;
+ }
}
}
+ catch (Exception ex) when (ex is InvalidOperationException or ArgumentException)
+ {
+ // Semantic analysis can fail in edge cases (e.g., incomplete compilation state).
+ // That's fine - we already tried syntax-based detection above.
+ }
- // Fallback: Syntax-based detection when semantic analysis fails
- // This ensures consistent behavior across TFMs
- return IsFrameworkAssertionBySyntax(invocation);
+ return false;
}
///
@@ -445,12 +555,28 @@ private bool IsFrameworkAssertionBySyntax(InvocationExpressionSyntax invocation)
return false;
}
- var targetType = memberAccess.Expression.ToString();
+ // Extract the simple type name from potentially qualified names
+ // e.g., "NUnit.Framework.Assert" -> "Assert", "Assert" -> "Assert"
+ var targetType = ExtractSimpleTypeName(memberAccess.Expression);
var methodName = memberAccess.Name.Identifier.Text;
return IsKnownAssertionTypeBySyntax(targetType, methodName);
}
+ ///
+ /// Extracts the simple type name from an expression.
+ /// Handles qualified names like "NUnit.Framework.Assert" by returning just "Assert".
+ ///
+ private static string ExtractSimpleTypeName(ExpressionSyntax expression)
+ {
+ return expression switch
+ {
+ IdentifierNameSyntax identifier => identifier.Identifier.Text,
+ MemberAccessExpressionSyntax memberAccess => memberAccess.Name.Identifier.Text,
+ _ => expression.ToString()
+ };
+ }
+
///
/// Checks if the target type and method name match known framework assertion patterns.
/// Override in derived classes to provide framework-specific patterns.
diff --git a/TUnit.Analyzers.CodeFixers/NUnitMigrationCodeFixProvider.cs b/TUnit.Analyzers.CodeFixers/NUnitMigrationCodeFixProvider.cs
index 35565a5051..fb439abeda 100644
--- a/TUnit.Analyzers.CodeFixers/NUnitMigrationCodeFixProvider.cs
+++ b/TUnit.Analyzers.CodeFixers/NUnitMigrationCodeFixProvider.cs
@@ -660,17 +660,15 @@ private UsingStatementSyntax CreateUsingMultipleStatement(ExpressionStatementSyn
protected override bool IsFrameworkAssertionNamespace(string namespaceName)
{
- // Exclude NUnit.Framework.Legacy - ClassicAssert should not be converted
- return (namespaceName == "NUnit.Framework" || namespaceName.StartsWith("NUnit.Framework."))
- && namespaceName != "NUnit.Framework.Legacy";
+ // Include NUnit.Framework.Legacy - ClassicAssert should be converted to TUnit assertions
+ return namespaceName == "NUnit.Framework" || namespaceName.StartsWith("NUnit.Framework.");
}
protected override bool IsKnownAssertionTypeBySyntax(string targetType, string methodName)
{
// NUnit assertion types that can be detected by syntax
- // NOTE: ClassicAssert is NOT included because it's in NUnit.Framework.Legacy namespace
- // and should not be auto-converted. The semantic check excludes it properly.
- return targetType is "Assert" or "CollectionAssert" or "StringAssert" or "FileAssert" or "DirectoryAssert";
+ // ClassicAssert is in NUnit.Framework.Legacy and should be converted
+ return targetType is "Assert" or "ClassicAssert" or "CollectionAssert" or "StringAssert" or "FileAssert" or "DirectoryAssert";
}
protected override ExpressionSyntax? ConvertAssertionIfNeeded(InvocationExpressionSyntax invocation)
@@ -717,15 +715,33 @@ protected override bool IsKnownAssertionTypeBySyntax(string targetType, string m
}
// Handle classic assertions like Assert.AreEqual, ClassicAssert.AreEqual, etc.
- if (invocation.Expression is MemberAccessExpressionSyntax classicMemberAccess &&
- classicMemberAccess.Expression is IdentifierNameSyntax { Identifier.Text: "Assert" or "ClassicAssert" })
+ // Also handles qualified names like NUnit.Framework.Assert.AreEqual
+ if (invocation.Expression is MemberAccessExpressionSyntax classicMemberAccess)
{
- return ConvertClassicAssertion(invocation, classicMemberAccess.Name.Identifier.Text);
+ var typeName = GetSimpleTypeName(classicMemberAccess.Expression);
+ if (typeName is "Assert" or "ClassicAssert")
+ {
+ return ConvertClassicAssertion(invocation, classicMemberAccess.Name.Identifier.Text);
+ }
}
return null;
}
-
+
+ ///
+ /// Extracts the simple type name from an expression.
+ /// Handles both simple identifiers and qualified names like "NUnit.Framework.Assert".
+ ///
+ private static string GetSimpleTypeName(ExpressionSyntax expression)
+ {
+ return expression switch
+ {
+ IdentifierNameSyntax identifier => identifier.Identifier.Text,
+ MemberAccessExpressionSyntax memberAccess => memberAccess.Name.Identifier.Text,
+ _ => expression.ToString()
+ };
+ }
+
private ExpressionSyntax ConvertAssertThat(InvocationExpressionSyntax invocation)
{
var arguments = invocation.ArgumentList.Arguments;
diff --git a/TUnit.Analyzers.CodeFixers/XUnitMigrationCodeFixProvider.cs b/TUnit.Analyzers.CodeFixers/XUnitMigrationCodeFixProvider.cs
index 411ce36b9a..aabff8af2e 100644
--- a/TUnit.Analyzers.CodeFixers/XUnitMigrationCodeFixProvider.cs
+++ b/TUnit.Analyzers.CodeFixers/XUnitMigrationCodeFixProvider.cs
@@ -16,7 +16,7 @@ public class XUnitMigrationCodeFixProvider : BaseMigrationCodeFixProvider
protected override string DiagnosticId => Rules.XunitMigration.Id;
protected override string CodeFixTitle => Rules.XunitMigration.Title.ToString();
- protected override bool ShouldAddTUnitUsings() => false;
+ protected override bool ShouldAddTUnitUsings() => true;
protected override AttributeRewriter CreateAttributeRewriter(Compilation compilation)
{
@@ -798,15 +798,33 @@ protected override bool IsKnownAssertionTypeBySyntax(string targetType, string m
return null;
}
- if (invocation.Expression is MemberAccessExpressionSyntax memberAccess &&
- memberAccess.Expression is IdentifierNameSyntax { Identifier.Text: "Assert" })
+ // Handle both simple (Assert.Equal) and qualified (Xunit.Assert.Equal) names
+ if (invocation.Expression is MemberAccessExpressionSyntax memberAccess)
{
- return ConvertXUnitAssertion(invocation, memberAccess.Name.Identifier.Text, memberAccess.Name);
+ var typeName = GetSimpleTypeName(memberAccess.Expression);
+ if (typeName == "Assert")
+ {
+ return ConvertXUnitAssertion(invocation, memberAccess.Name.Identifier.Text, memberAccess.Name);
+ }
}
return null;
}
+ ///
+ /// Extracts the simple type name from an expression.
+ /// Handles both simple identifiers and qualified names like "Xunit.Assert".
+ ///
+ private static string GetSimpleTypeName(ExpressionSyntax expression)
+ {
+ return expression switch
+ {
+ IdentifierNameSyntax identifier => identifier.Identifier.Text,
+ MemberAccessExpressionSyntax memberAccess => memberAccess.Name.Identifier.Text,
+ _ => expression.ToString()
+ };
+ }
+
private ExpressionSyntax? ConvertXUnitAssertion(InvocationExpressionSyntax invocation, string methodName, SimpleNameSyntax nameNode)
{
var arguments = invocation.ArgumentList.Arguments;
@@ -845,21 +863,21 @@ protected override bool IsKnownAssertionTypeBySyntax(string targetType, string m
"NotSame" when arguments.Count >= 2 =>
CreateTUnitAssertion("IsNotSameReferenceAs", arguments[1].Expression, arguments[0]),
- // String/Collection contains
+ // String/Collection contains - use collection assertion for proper overload resolution
"Contains" when arguments.Count >= 2 =>
- CreateTUnitAssertion("Contains", arguments[1].Expression, arguments[0]),
+ CreateTUnitCollectionAssertion("Contains", arguments[1].Expression, arguments[0]),
"DoesNotContain" when arguments.Count >= 2 =>
- CreateTUnitAssertion("DoesNotContain", arguments[1].Expression, arguments[0]),
+ CreateTUnitCollectionAssertion("DoesNotContain", arguments[1].Expression, arguments[0]),
"StartsWith" when arguments.Count >= 2 =>
CreateTUnitAssertion("StartsWith", arguments[1].Expression, arguments[0]),
"EndsWith" when arguments.Count >= 2 =>
CreateTUnitAssertion("EndsWith", arguments[1].Expression, arguments[0]),
- // Empty/Not empty
+ // Empty/Not empty - use collection assertion for proper overload resolution
"Empty" when arguments.Count >= 1 =>
- CreateTUnitAssertion("IsEmpty", arguments[0].Expression),
+ CreateTUnitCollectionAssertion("IsEmpty", arguments[0].Expression),
"NotEmpty" when arguments.Count >= 1 =>
- CreateTUnitAssertion("IsNotEmpty", arguments[0].Expression),
+ CreateTUnitCollectionAssertion("IsNotEmpty", arguments[0].Expression),
// Exception assertions
"Throws" => ConvertThrows(invocation, nameNode),
@@ -878,29 +896,29 @@ protected override bool IsKnownAssertionTypeBySyntax(string targetType, string m
"NotInRange" when arguments.Count >= 3 =>
CreateTUnitAssertion("IsNotInRange", arguments[0].Expression, arguments[1], arguments[2]),
- // Collection assertions
+ // Collection assertions - use collection assertion for proper overload resolution
"Single" when arguments.Count >= 1 =>
- CreateTUnitAssertion("HasSingleItem", arguments[0].Expression),
+ CreateTUnitCollectionAssertion("HasSingleItem", arguments[0].Expression),
"All" when arguments.Count >= 2 =>
CreateAllAssertion(arguments[0].Expression, arguments[1].Expression),
- // Subset/superset
+ // Subset/superset - use collection assertion for proper overload resolution
"Subset" when arguments.Count >= 2 =>
- CreateTUnitAssertion("IsSubsetOf", arguments[0].Expression, arguments[1]),
+ CreateTUnitCollectionAssertion("IsSubsetOf", arguments[0].Expression, arguments[1]),
"Superset" when arguments.Count >= 2 =>
- CreateTUnitAssertion("IsSupersetOf", arguments[0].Expression, arguments[1]),
+ CreateTUnitCollectionAssertion("IsSupersetOf", arguments[0].Expression, arguments[1]),
"ProperSubset" when arguments.Count >= 2 =>
CreateProperSubsetWithTodo(arguments),
"ProperSuperset" when arguments.Count >= 2 =>
CreateProperSupersetWithTodo(arguments),
- // Unique items
+ // Unique items - use collection assertion for proper overload resolution
"Distinct" when arguments.Count >= 1 =>
- CreateTUnitAssertion("HasDistinctItems", arguments[0].Expression),
+ CreateTUnitCollectionAssertion("HasDistinctItems", arguments[0].Expression),
- // Equivalent (order independent)
+ // Equivalent (order independent) - use collection assertion for proper overload resolution
"Equivalent" when arguments.Count >= 2 =>
- CreateTUnitAssertion("IsEquivalentTo", arguments[1].Expression, arguments[0]),
+ CreateTUnitCollectionAssertion("IsEquivalentTo", arguments[1].Expression, arguments[0]),
// Regex assertions
"Matches" when arguments.Count >= 2 =>
@@ -951,7 +969,7 @@ private ExpressionSyntax CreateCollectionWithTodo(SeparatedSyntaxList(action) -> await Assert.ThrowsAsync(action)
- // Note: ThrowsAny accepts derived types, ThrowsAsync should work similarly
+ // xUnit Assert.ThrowsAny(Action) -> TUnit Assert.Throws(Action)
+ // Both are synchronous - ThrowsAny accepts derived types, TUnit's Throws does too
+ if (nameNode is GenericNameSyntax genericName)
+ {
+ var exceptionType = genericName.TypeArgumentList.Arguments[0];
+ var action = invocation.ArgumentList.Arguments[0].Expression;
+
+ // Keep it synchronous - TUnit's Assert.Throws(Action) accepts derived types
+ return SyntaxFactory.InvocationExpression(
+ SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ SyntaxFactory.IdentifierName("Assert"),
+ SyntaxFactory.GenericName("Throws")
+ .WithTypeArgumentList(
+ SyntaxFactory.TypeArgumentList(
+ SyntaxFactory.SingletonSeparatedList(exceptionType)
+ )
+ )
+ ),
+ SyntaxFactory.ArgumentList(
+ SyntaxFactory.SingletonSeparatedList(
+ SyntaxFactory.Argument(action)
+ )
+ )
+ );
+ }
+
+ return CreateTUnitAssertion("Throws", invocation.ArgumentList.Arguments[0].Expression);
+ }
+
+ private ExpressionSyntax ConvertThrowsAnyAsync(InvocationExpressionSyntax invocation, SimpleNameSyntax nameNode)
+ {
+ // xUnit Assert.ThrowsAnyAsync(Func) -> await Assert.ThrowsAsync(Func)
+ // ThrowsAnyAsync accepts derived types, TUnit's ThrowsAsync does too
if (nameNode is GenericNameSyntax genericName)
{
var exceptionType = genericName.TypeArgumentList.Arguments[0];
@@ -1185,13 +1205,7 @@ private ExpressionSyntax ConvertThrowsAny(InvocationExpressionSyntax invocation,
return SyntaxFactory.AwaitExpression(awaitKeyword, invocationExpression);
}
- return CreateTUnitAssertion("Throws", invocation.ArgumentList.Arguments[0].Expression);
- }
-
- private ExpressionSyntax ConvertThrowsAnyAsync(InvocationExpressionSyntax invocation, SimpleNameSyntax nameNode)
- {
- // Same as ThrowsAny but for async
- return ConvertThrowsAny(invocation, nameNode);
+ return CreateTUnitAssertion("ThrowsAsync", invocation.ArgumentList.Arguments[0].Expression);
}
private ExpressionSyntax ConvertIsNotType(InvocationExpressionSyntax invocation, SimpleNameSyntax nameNode)
@@ -1237,17 +1251,20 @@ private ExpressionSyntax ConvertIsNotType(InvocationExpressionSyntax invocation,
private ExpressionSyntax ConvertThrows(InvocationExpressionSyntax invocation, SimpleNameSyntax nameNode)
{
- // Assert.Throws(action) -> await Assert.ThrowsAsync(action)
+ // xUnit Assert.Throws(Action) -> TUnit Assert.Throws(Action)
+ // Both are synchronous and return the exception directly
+ // NO async conversion needed - TUnit has a sync version that matches xUnit's signature
if (nameNode is GenericNameSyntax genericName)
{
var exceptionType = genericName.TypeArgumentList.Arguments[0];
var action = invocation.ArgumentList.Arguments[0].Expression;
- var invocationExpression = SyntaxFactory.InvocationExpression(
+ // Keep it synchronous - TUnit's Assert.Throws(Action) returns TException directly
+ return SyntaxFactory.InvocationExpression(
SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
SyntaxFactory.IdentifierName("Assert"),
- SyntaxFactory.GenericName("ThrowsAsync")
+ SyntaxFactory.GenericName("Throws")
.WithTypeArgumentList(
SyntaxFactory.TypeArgumentList(
SyntaxFactory.SingletonSeparatedList(exceptionType)
@@ -1260,13 +1277,9 @@ private ExpressionSyntax ConvertThrows(InvocationExpressionSyntax invocation, Si
)
)
);
-
- var awaitKeyword = SyntaxFactory.Token(SyntaxKind.AwaitKeyword)
- .WithTrailingTrivia(SyntaxFactory.Space);
- return SyntaxFactory.AwaitExpression(awaitKeyword, invocationExpression);
}
- // Fallback
+ // Fallback for non-generic Throws
return CreateTUnitAssertion("Throws", invocation.ArgumentList.Arguments[0].Expression);
}
diff --git a/TUnit.Analyzers.Tests/MSTestMigrationAnalyzerTests.cs b/TUnit.Analyzers.Tests/MSTestMigrationAnalyzerTests.cs
index edbea903d5..8140ac97c4 100644
--- a/TUnit.Analyzers.Tests/MSTestMigrationAnalyzerTests.cs
+++ b/TUnit.Analyzers.Tests/MSTestMigrationAnalyzerTests.cs
@@ -1,3 +1,4 @@
+using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using CodeFixer = TUnit.Analyzers.Tests.Verifiers.CSharpCodeFixVerifier;
@@ -47,10 +48,6 @@ public void MyMethod() { }
""",
Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0),
$$"""
- using TUnit.Core;
- using TUnit.Assertions;
- using static TUnit.Assertions.Assert;
- using TUnit.Assertions.Extensions;
public class MyClass
{
@@ -78,11 +75,6 @@ public void MyMethod() { }
""",
Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0),
"""
- using TUnit.Core;
- using TUnit.Assertions;
- using static TUnit.Assertions.Assert;
- using TUnit.Assertions.Extensions;
-
public class MyClass
{
[Test]
@@ -115,10 +107,6 @@ public void MyMethod()
Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0),
"""
using System.Threading.Tasks;
- using TUnit.Core;
- using TUnit.Assertions;
- using static TUnit.Assertions.Assert;
- using TUnit.Assertions.Extensions;
public class MyClass
{
@@ -167,10 +155,6 @@ public void MyMethod() { }
""",
Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0),
"""
- using TUnit.Core;
- using TUnit.Assertions;
- using static TUnit.Assertions.Assert;
- using TUnit.Assertions.Extensions;
public class MyClass
{
@@ -202,10 +186,6 @@ public void MyMethod() { }
""",
Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0),
"""
- using TUnit.Core;
- using TUnit.Assertions;
- using static TUnit.Assertions.Assert;
- using TUnit.Assertions.Extensions;
public class MyClass
{
@@ -244,10 +224,6 @@ public void MyMethod() { }
""",
Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0),
"""
- using TUnit.Core;
- using TUnit.Assertions;
- using static TUnit.Assertions.Assert;
- using TUnit.Assertions.Extensions;
public class MyClass
{
@@ -287,10 +263,6 @@ public void MyMethod()
Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0),
"""
using System.Threading.Tasks;
- using TUnit.Core;
- using TUnit.Assertions;
- using static TUnit.Assertions.Assert;
- using TUnit.Assertions.Extensions;
public class MyClass
{
@@ -329,10 +301,6 @@ public void StringTests()
Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0),
"""
using System.Threading.Tasks;
- using TUnit.Core;
- using TUnit.Assertions;
- using static TUnit.Assertions.Assert;
- using TUnit.Assertions.Extensions;
public class MyClass
{
@@ -377,10 +345,6 @@ public void OuterTest()
Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0),
"""
using System.Threading.Tasks;
- using TUnit.Core;
- using TUnit.Assertions;
- using static TUnit.Assertions.Assert;
- using TUnit.Assertions.Extensions;
public class OuterClass
{
@@ -424,10 +388,6 @@ public void GenericTest()
Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0),
"""
using System.Threading.Tasks;
- using TUnit.Core;
- using TUnit.Assertions;
- using static TUnit.Assertions.Assert;
- using TUnit.Assertions.Extensions;
public class GenericTestClass
{
@@ -514,10 +474,6 @@ public static void ClassTeardown()
"""
using System;
using System.Threading.Tasks;
- using TUnit.Core;
- using TUnit.Assertions;
- using static TUnit.Assertions.Assert;
- using TUnit.Assertions.Extensions;
public class CompleteTestClass
{
@@ -615,10 +571,6 @@ public void TestMultipleAssertionTypes()
Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0),
"""
using System.Threading.Tasks;
- using TUnit.Core;
- using TUnit.Assertions;
- using static TUnit.Assertions.Assert;
- using TUnit.Assertions.Extensions;
public class MyClass
{
@@ -672,10 +624,6 @@ public void TestReferences()
Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0),
"""
using System.Threading.Tasks;
- using TUnit.Core;
- using TUnit.Assertions;
- using static TUnit.Assertions.Assert;
- using TUnit.Assertions.Extensions;
public class MyClass
{
@@ -717,10 +665,6 @@ public void TestWithMessages()
Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0),
"""
using System.Threading.Tasks;
- using TUnit.Core;
- using TUnit.Assertions;
- using static TUnit.Assertions.Assert;
- using TUnit.Assertions.Extensions;
public class MyClass
{
@@ -741,13 +685,16 @@ public async Task TestWithMessages()
[Test]
public async Task MSTest_Assertions_With_FormatStrings_Converted()
{
+ // Note: The diagnostic is on [TestMethod] because Assert.AreEqual with format strings
+ // isn't a valid MSTest overload, so semantic model doesn't resolve it.
+ // The analyzer detects the method attribute instead of the Assert call.
await CodeFixer.VerifyCodeFixAsync(
"""
using Microsoft.VisualStudio.TestTools.UnitTesting;
- {|#0:public class MyClass|}
+ public class MyClass
{
- [TestMethod]
+ {|#0:[TestMethod]|}
public void TestWithFormatStrings()
{
int x = 5;
@@ -759,10 +706,6 @@ public void TestWithFormatStrings()
Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0),
"""
using System.Threading.Tasks;
- using TUnit.Core;
- using TUnit.Assertions;
- using static TUnit.Assertions.Assert;
- using TUnit.Assertions.Extensions;
public class MyClass
{
@@ -784,14 +727,16 @@ public async Task MSTest_Assertions_With_Comparer_AddsTodoComment()
{
// When a comparer is detected (via semantic or syntax-based detection),
// a TODO comment is added explaining that TUnit uses different comparison semantics.
+ // Note: The diagnostic is on [TestMethod] because Assert.AreEqual with comparer
+ // isn't a valid MSTest overload, so semantic model doesn't resolve it.
await CodeFixer.VerifyCodeFixAsync(
"""
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
- {|#0:public class MyClass|}
+ public class MyClass
{
- [TestMethod]
+ {|#0:[TestMethod]|}
public void TestWithComparer()
{
var comparer = StringComparer.OrdinalIgnoreCase;
@@ -803,10 +748,6 @@ public void TestWithComparer()
"""
using System.Collections.Generic;
using System.Threading.Tasks;
- using TUnit.Core;
- using TUnit.Assertions;
- using static TUnit.Assertions.Assert;
- using TUnit.Assertions.Extensions;
public class MyClass
{
@@ -841,10 +782,6 @@ public void TestMethod()
""",
Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0),
"""
- using TUnit.Core;
- using TUnit.Assertions;
- using static TUnit.Assertions.Assert;
- using TUnit.Assertions.Extensions;
public class MyClass
{
@@ -877,10 +814,6 @@ public void TestMethod()
""",
Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0),
"""
- using TUnit.Core;
- using TUnit.Assertions;
- using static TUnit.Assertions.Assert;
- using TUnit.Assertions.Extensions;
public class MyClass
{
@@ -913,10 +846,6 @@ public void TestMethod()
""",
Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0),
"""
- using TUnit.Core;
- using TUnit.Assertions;
- using static TUnit.Assertions.Assert;
- using TUnit.Assertions.Extensions;
public class MyClass
{
@@ -953,10 +882,6 @@ public void TestMethod()
"""
using System;
using System.Threading.Tasks;
- using TUnit.Core;
- using TUnit.Assertions;
- using static TUnit.Assertions.Assert;
- using TUnit.Assertions.Extensions;
public class MyClass
{
@@ -998,10 +923,6 @@ public async Task TestMethodAsync()
"""
using System;
using System.Threading.Tasks;
- using TUnit.Core;
- using TUnit.Assertions;
- using static TUnit.Assertions.Assert;
- using TUnit.Assertions.Extensions;
public class MyClass
{
@@ -1043,10 +964,6 @@ public void TestMethod()
"""
using System.IO;
using System.Threading.Tasks;
- using TUnit.Core;
- using TUnit.Assertions;
- using static TUnit.Assertions.Assert;
- using TUnit.Assertions.Extensions;
public class MyClass
{
@@ -1085,10 +1002,6 @@ public void TestMethod()
"""
using System.IO;
using System.Threading.Tasks;
- using TUnit.Core;
- using TUnit.Assertions;
- using static TUnit.Assertions.Assert;
- using TUnit.Assertions.Extensions;
public class MyClass
{
@@ -1127,10 +1040,6 @@ public void TestMethod()
"""
using System.IO;
using System.Threading.Tasks;
- using TUnit.Core;
- using TUnit.Assertions;
- using static TUnit.Assertions.Assert;
- using TUnit.Assertions.Extensions;
public class MyClass
{
@@ -1169,10 +1078,6 @@ public void TestMethod()
"""
using System.IO;
using System.Threading.Tasks;
- using TUnit.Core;
- using TUnit.Assertions;
- using static TUnit.Assertions.Assert;
- using TUnit.Assertions.Extensions;
public class MyClass
{
@@ -1188,6 +1093,248 @@ public async Task TestMethod()
);
}
+ [Test]
+ public async Task MSTest_KitchenSink_Comprehensive_Migration()
+ {
+ // This test combines MANY MSTest patterns together to ensure the code fixer
+ // can handle complex real-world scenarios in a single pass.
+ await CodeFixer.VerifyCodeFixAsync(
+ """
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+ using System;
+ using System.Collections.Generic;
+
+ {|#0:[TestClass]|}
+ public class KitchenSinkTests
+ {
+ private static List _log;
+ private int _counter;
+
+ [ClassInitialize]
+ public static void ClassSetup(TestContext context)
+ {
+ _log = new List();
+ }
+
+ [TestInitialize]
+ public void TestSetup()
+ {
+ _counter = 0;
+ }
+
+ [TestMethod]
+ public void BasicTest()
+ {
+ Assert.IsTrue(_counter == 0);
+ Assert.AreEqual(0, _counter);
+ Assert.IsNotNull(_log);
+ }
+
+ [TestMethod]
+ [DataRow(1, 2, 3)]
+ [DataRow(10, 20, 30)]
+ [DataRow(-1, 1, 0)]
+ public void ParameterizedTest(int a, int b, int expected)
+ {
+ var result = a + b;
+ Assert.AreEqual(expected, result);
+ }
+
+ [TestMethod]
+ [DynamicData(nameof(GetTestData))]
+ public void DataSourceTest(string input, int expectedLength)
+ {
+ Assert.AreEqual(expectedLength, input.Length);
+ Assert.IsNotNull(input);
+ }
+
+ public static IEnumerable