How to Migrate Between Test Frameworks Like xUnit to MSTest or VSTest to MTP

Migrating .NET test projects requires replacing NuGet packages, remapping attributes and assertions, converting fixtures to lifecycle hooks, and validating test parity to ensure identical behavior across xUnit, MSTest, and the Microsoft Testing Platform.

The dotnet/skills repository provides authoritative migration guidance through dedicated skills that automate the transition between popular .NET testing frameworks. Whether standardizing on MSTest v4 or modernizing from the classic VSTest runner to the new Microsoft Testing Platform (MTP), systematic migration prevents silent behavioral changes in parallelization and exception handling.

Understanding Architectural Differences

Before modifying code, recognize how each framework handles discovery, execution, and lifecycle management. According to the migration skills in dotnet/skills, the most significant behavioral divergences involve parallelization defaults and exception assertion semantics.

Aspect xUnit MSTest v4 VSTest MTP
Discovery & Execution Uses xunit.runner.visualstudio (VSTest) or xunit.v3.mtp-v* (MTP) Uses MSTest metapackage or MSTest.Sdk Classic Microsoft.NET.Test.Sdk New Microsoft.Testing.Platform engine
Parallelization Parallel across classes, serial inside class Serial by default; requires [assembly: Parallelize] Serial (unless overridden) Mirrors MSTest defaults
Lifecycle Hooks Constructors, IAsyncLifetime, IClassFixture<T>, ICollectionFixture<T> Constructors, IDisposable, [TestInitialize], [TestCleanup], [ClassInitialize], [ClassCleanup] Same as MSTest Same as MSTest
Data-Driven Tests [InlineData], [MemberData], [ClassData] [DataRow], [DynamicData] Same as MSTest Same as MSTest
Exact Exception Types Assert.Throws<T> requires exact type Assert.ThrowsExactly<T> for exact type, Assert.Throws<T> for derived types Same as MSTest Same as MSTest

Failing to address these differences—particularly parallelization and exception assertions—can cause tests to pass silently or become flaky after migration.

Migrating from xUnit to MSTest

The skill defined in plugins/dotnet-test-migration/skills/migrate-xunit-to-mstest/SKILL.md outlines a five-phase migration: assess, replace packages, rewrite code, adjust metadata, and verify.

Package Replacement

Remove all xUnit packages and introduce the MSTest metapackage. In your .csproj file:

<!-- Remove xUnit packages -->
<ItemGroup>
  <PackageReference Remove="xunit" />
  <PackageReference Remove="xunit.assert" />
  <PackageReference Remove="xunit.runner.visualstudio" />
</ItemGroup>

<!-- Add MSTest -->
<ItemGroup>
  <PackageReference Include="MSTest" Version="4.1.0" />
</ItemGroup>

Alternatively, use MSTest.Sdk as the project SDK with <UseVSTest>true</UseVSTest> if maintaining VSTest compatibility during transition.

Attribute Mapping

Replace xUnit attributes with their MSTest equivalents as documented in references/mapping-cheatsheet.md:

xUnit MSTest
[Fact] [TestMethod]
[Theory] [TestMethod] (with [DataRow] or [DynamicData])
[Fact(Skip="...")] [Ignore("...")]
[Trait("Category","Unit")] [TestCategory("Unit")]
[Trait("Owner","alice")] [TestProperty("Owner","alice")]

Convert theory tests to data-driven tests:

// xUnit
[Theory]
[InlineData(1, true)]
[InlineData(2, false)]
public void IsEven(int n, bool expected) => Assert.Equal(expected, n % 2 == 0);
// MSTest
[DataTestMethod]
[DataRow(1, true)]
[DataRow(2, false)]
public void IsEven(int n, bool expected)
{
    Assert.AreEqual(expected, n % 2 == 0);
}

Assertion Conversions

Update assertions to match MSTest semantics. Critical differences include exact type matching:

xUnit MSTest
Assert.Equal(a, b) Assert.AreEqual(a, b)
Assert.Throws<T>(action) Assert.ThrowsExactly<T>(action)
Assert.ThrowsAny<T>(action) Assert.Throws<T>(action)
// xUnit
Assert.Throws<InvalidOperationException>(() => service.DoWork());

// MSTest
Assert.ThrowsExactly<InvalidOperationException>(() => service.DoWork());

Fixture and Lifecycle Migration

Convert IClassFixture<T> to static fields with [ClassInitialize] and [ClassCleanup]:

// xUnit
public class DatabaseFixture : IDisposable { /* ... */ }

public class RepoTests : IClassFixture<DatabaseFixture>
{
    private readonly DatabaseFixture _db;
    public RepoTests(DatabaseFixture db) => _db = db;
}
// MSTest
[TestClass]
public sealed class RepoTests
{
    private static DatabaseFixture? _db;

    [ClassInitialize]
    public static void Init(TestContext _) => _db = new DatabaseFixture();

    [ClassCleanup]
    public static void Cleanup() => _db?.Dispose();
}

For ICollectionFixture<T>, either hoist to [AssemblyInitialize] (if scope widening is acceptable) or maintain per-class static fixtures with [DoNotParallelize] to preserve serialization.

Replace ITestOutputHelper with TestContext:

// xUnit
public MyTests(ITestOutputHelper output) => _out = output;

// MSTest
public MyTests(TestContext ctx) => _testContext = ctx;

Parallelization Configuration

xUnit runs test classes in parallel by default. MSTest requires explicit configuration. Add to any .cs file at assembly scope:

[assembly: Parallelize(Workers = 0, Scope = ExecutionScope.ClassLevel)]

Omit this attribute to maintain serial execution (MSTest's default).

Migrating from VSTest to Microsoft Testing Platform (MTP)

The skill in plugins/dotnet-test-migration/skills/migrate-vstest-to-mtp/SKILL.md handles runner transitions without changing the underlying test framework.

SDK and Project File Changes

Switch from the classic test SDK to the Microsoft Testing Platform SDK:

<!-- Before (VSTest) -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
    <PackageReference Include="MSTest.TestAdapter" Version="3.0.2" />
    <PackageReference Include="MSTest.TestFramework" Version="3.0.2" />
  </ItemGroup>
</Project>

<!-- After (MTP) -->
<Project Sdk="Microsoft.Testing.Platform.Sdk/1.0.0">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <UseMicrosoftTestingPlatformRunner>true</UseMicrosoftTestingPlatformRunner>
  </PropertyGroup>
</Project>

Remove explicit Microsoft.NET.Test.Sdk references when using the MTP SDK, as the platform pulls the correct runtime automatically. Preserve parallelization settings using [Parallelize] attributes, as MTP respects MSTest's parallelization configuration.

Validation and Verification

After migration, execute the validation checklist from the SKILL file to ensure behavioral parity:

dotnet build               # Must succeed without warnings

dotnet test --no-build     # Should discover identical test count

Compare the pass/fail counts against the baseline recorded during initial assessment. Pay special attention to tests using exception assertions or relying on specific parallelization behavior, as these are the most common sources of migration-induced failures.

Summary

  • Assess first using the platform-detection logic in dotnet/skills to identify current packages and target frameworks.
  • Replace packages by removing xUnit or VSTest SDK references and adding MSTest 4.1+ or Microsoft.Testing.Platform.Sdk.
  • Remap attributes using [TestMethod], [DataRow], and [TestCategory] instead of [Fact], [InlineData], and [Trait].
  • Convert assertions to Assert.ThrowsExactly<T> for exact type matching and Assert.AreEqual for equality checks.
  • Migrate fixtures from IClassFixture<T> to static fields with [ClassInitialize]/[ClassCleanup].
  • Configure parallelization explicitly with [Parallelize] to match xUnit's default class-level parallelism.
  • Validate by comparing test counts and results before and after migration.

Frequently Asked Questions

What is the difference between MSTest and Microsoft Testing Platform?

MSTest is the test framework (attributes, assertions, lifecycle), while the Microsoft Testing Platform (MTP) is the test runner (discovery, execution, reporting). You can run MSTest tests using either the classic VSTest runner or the new MTP runner. The migration from VSTest to MTP specifically swaps the execution engine while keeping your MSTest code unchanged.

Do I need to change all my test classes when switching from xUnit to MSTest?

Yes, you must update attributes and assertions, but constructors and basic structure remain similar. You need to replace [Fact] with [TestMethod], convert [Theory] to [DataTestMethod] with [DataRow], and update using statements from Xunit to Microsoft.VisualStudio.TestTools.UnitTesting. The dotnet/skills repository provides a mapping cheatsheet at references/mapping-cheatsheet.md to automate these conversions.

Will my tests run in parallel after migrating to MSTest?

Not by default. MSTest runs tests serially unless you explicitly add [assembly: Parallelize(Workers = 0, Scope = ExecutionScope.ClassLevel)] to an assembly-level file. This is the opposite of xUnit's default behavior, which parallelizes across classes. If your tests rely on parallel execution for performance, you must add this attribute during migration.

Can I migrate gradually or must I convert all projects at once?

You can migrate incrementally. The Microsoft.NET.Test.Sdk supports multiple test frameworks in the same solution. However, when migrating from VSTest to MTP, you must choose the runner at the project level—individual projects can use different runners within the same solution. The dotnet/skills agents support analyzing and converting projects one at a time to manage risk.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →