dotnet/migrate-xunit-to-mstest
> Convert .NET test projects from xUnit.net v2 or v3 to MSTest v4. Use for replacing xunit packages, [Fact]/[Theory], xUnit assertions, fixtures, ITestOutputHelper, traits, skips, and xUnit parallelization with MSTest equivalents while preserving the current VSTest or MTP runner. from NUnit/TUnit, or runner-only VSTest to MTP migrations.
npx skills add https://github.com/dotnet/skills --skill migrate-xunit-to-mstest
Convert xUnit.net v2 or v3 tests to MSTest v4 without changing the target framework or test platform. A successful migration builds, discovers the same tests, and preserves pass/fail results and execution semantics.
Use this skill only when the project contains xUnit packages or source and the user wants MSTest. If the project already uses MSTest and contains no xUnit tests, report that no framework migration is needed and make no changes.
Do not combine this framework conversion with a target-framework upgrade or VSTest/MTP migration. Complete and verify one migration before starting another.
For detailed mappings and examples, search references/mapping-cheatsheet.md for constructs actually present in the project and read only the matching sections. Do not load or reproduce the whole reference.
For a routine project migration, converge in four phases: one batched discovery read/search, one edit pass, one dotnet test, and one concise result. Do not:
dotnet test --no-restore unless restore is already known to be currentdotnet test is sufficientUse an existing CI/test result as the parity baseline when available. Run a new pre-edit baseline only when counts are unavailable and the migration contains data-driven tests, fixtures, skips, custom extensions, shared state, or other behavior whose parity cannot be established from source alone.
Directory.Build.props, Directory.Packages.props, global.json, and runner configuration, and search the source for the high-risk constructs below.xunit 2.x and related packages -> xUnit v2xunit.v3 or xunit.v3.* -> xUnit v3platform-detection only when the platform is ambiguous, and preserve the detected platform.IClassFixture, ICollectionFixture, CollectionDefinition, custom FactAttribute/TheoryAttribute/DataAttributeAssert.Throws, ThrowsAny, IsType, Record.Exception, event assertionsITestOutputHelper, TestContext.Current, IAsyncLifetimeCollectionBehavior, xunit.runner.json, shared static or external stateRemove xUnit packages from project files and central package files. This includes xunit*, xunit.v3.*, xunit.runner.visualstudio, YTest.MTP.XUnit2, and xUnit-specific companion packages that are being replaced.
Default to the MSTest v4 metapackage for an incremental conversion:
<PackageReference Include="MSTest" Version="4.1.0" />
This keeps VSTest available through the metapackage's compatible Microsoft.NET.Test.Sdk dependency. Remove a stale explicit Microsoft.NET.Test.Sdk reference or update it to the minimum required by the chosen MSTest version (MSTest 4.1.0 requires 18.0.1+); otherwise restore fails with NU1605. Use MSTest.Sdk only when the project already uses it elsewhere or the user explicitly requests it. MSTest.Sdk defaults to MTP, so add <UseVSTest>true</UseVSTest> when preserving VSTest.
Do not change TargetFramework. Remove xunit.runner.json only after porting its relevant settings.
Apply the common rewrites first:
| xUnit | MSTest |
|---|---|
| no class attribute | [TestClass] |
| [Fact] | [TestMethod] |
| [Theory] + [InlineData] | [TestMethod] + [DataRow] |
| [MemberData] | [DynamicData] |
| [Fact(Skip = "...")] | [TestMethod] + [Ignore("...")] |
| [Trait("Category", value)] | [TestCategory(value)] |
| [Trait("Owner", value)] | [Owner(value)] |
| other [Trait(key, value)] | [TestProperty(key, value)] |
| Assert.Equal / NotEqual | Assert.AreEqual / AreNotEqual |
| Assert.True / False | Assert.IsTrue / IsFalse |
| Assert.Null / NotNull | Assert.IsNull / IsNotNull |
Remove using Xunit; and using Xunit.Abstractions;. Add using Microsoft.VisualStudio.TestTools.UnitTesting; for the metapackage option; MSTest.Sdk supplies it as an implicit global using.
Preserve existing class inheritance. Do not mechanically seal classes.
Load the mapping cheatsheet for every high-risk construct found in Step 1. These rules are mandatory:
Assert.Throws<T> is exact-type and maps to MSTest Assert.ThrowsExactly<T>.Assert.ThrowsAny<T> permits derived types and maps to MSTest Assert.Throws<T>.Assert.IsType<T> is exact-type and maps to Assert.IsExactInstanceOfType<T>; Assert.IsAssignableFrom<T> maps to Assert.IsInstanceOfType<T>.Assert.Equal on sequences compares elements. Use Assert.AreSequenceEqual on MSTest 4.3+ or CollectionAssert.AreEqual with materialized lists on earlier v4; never replace sequence equality with reference-based Assert.AreEqual.[Ignore] and [Timeout] are modifiers; keep [TestMethod] so the test is discovered.[DataRow] values must exactly match parameter types.TestContext.Current.CancellationToken maps to an injected MSTest TestContext.CancellationToken; never replace it with CancellationToken.None or a new CancellationTokenSource.Owner is a reserved VSTest property. Map [Trait("Owner", value)] to [Owner(value)], not [TestProperty("Owner", value)].Assert.Collection, Assert.All, Assert.Equivalent, Record.Exception, event assertions) require an explicit manual rewrite. Never delete an assertion without replacing its verification.Apply the mechanical and semantic rewrites in one edit pass when the inventory makes the required mappings clear. Do not run an intermediate build by default; use compiler errors from final verification to drive only unresolved conversions.
IDisposable/IAsyncDisposable when valid. Map IAsyncLifetime to [TestInitialize]/[TestCleanup].IClassFixture<T> to class-scoped initialization and cleanup.ICollectionFixture<T>, preserve both sharing and serialization. Prefer a static Lazy<T> helper used by each member class; add [DoNotParallelize] only when the source collection disabled parallelization. Use assembly initialization only when the fixture is genuinely assembly-wide.ITestOutputHelper with injected or property-based MSTest TestContext.xUnit runs classes in parallel by default; MSTest runs them serially. Unless the source disabled parallelism, preserve xUnit behavior with:
[assembly: Parallelize(Workers = 0, Scope = ExecutionScope.ClassLevel)]
Never use ExecutionScope.MethodLevel to emulate xUnit. Before applying a fixture-scope or parallelization decision, state what the source shared or serialized and how the target preserves it.
dotnet test builds by default; run a separate build only when needed to isolate a compilation failure.DynamicData, or DataRow literal types[TestMethod] or incorrect runtime-skip conversionRun migrate-vstest-to-mtp separately if the user also wants MTP. Use writing-mstest-tests only after parity is established to polish the converted MSTest code.
Take dotnet/migrate-xunit-to-mstest from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.