Claude
Skills
Sign in
Back

migrate-xunit-to-mstest

Included with Lifetime
$97 forever

Migrate .NET test projects from xUnit.net (v2 or v3) to MSTest v4. USE FOR: convert/migrate xUnit tests to MSTest, replace xunit/xunit.v3 packages, port [Fact]/[Theory]/[InlineData]/[MemberData]/[ClassData] to [TestMethod]/[DataRow]/[DynamicData], port Assert.Equal/True/Throws/ThrowsAsync to Assert.AreEqual/IsTrue/ThrowsExactly/ThrowsExactlyAsync, port IClassFixture/ ICollectionFixture/IDisposable/IAsyncLifetime/ITestOutputHelper/[Trait]/[Fact(Skip)] to MSTest equivalents, preserve xUnit parallel-class default via [assembly: Parallelize(Scope = ClassLevel)], remove xunit.runner.json. DO NOT USE FOR: xUnit v2 -> v3 upgrade (use migrate-xunit-to-xunit-v3); MSTest -> xUnit, NUnit/TUnit -> MSTest (no skills exist); MSTest version upgrades (use migrate-mstest-v1v2-to-v3 or migrate-mstest-v3-to-v4); VSTest <-> MTP only (use migrate-vstest-to-mtp); general .NET upgrades.

Code Review

What this skill does


# xUnit -> MSTest Migration

Migrate a .NET test project from xUnit.net (v2 or v3) to MSTest v4. The outcome is a project that:

- References MSTest v4 packages (or `MSTest.Sdk` 4.x) instead of `xunit*` / `xunit.v3.*`
- Has every `[Fact]`/`[Theory]` rewritten as `[TestMethod]` and every assertion mapped to the MSTest equivalent
- Builds cleanly with the same target framework
- Passes the same set of tests (modulo intentional changes documented below)
- Preserves the **current test platform** (VSTest stays on VSTest; MTP stays on MTP)

This is a **cross-framework** migration. Do not bundle it with a version upgrade or a platform switch in the same pass -- if both are needed, do this skill first, commit, then run `migrate-mstest-v3-to-v4` (if you stopped on v3) or `migrate-vstest-to-mtp`.

## When to Use

- The project references `xunit`, `xunit.assert`, `xunit.core`, `xunit.extensibility.core`/`execution`, `xunit.abstractions`, or any `xunit.v3.*` package, and you want to switch to MSTest
- You want a single .NET test framework across a solution that today mixes xUnit and MSTest

## Inputs

| Input | Required | Description |
|-------|----------|-------------|
| Project or solution path | Yes | The `.csproj`, `.sln`, or `.slnx` containing xUnit test projects |
| Build command | No | How to build (e.g., `dotnet build`). Auto-detect if not provided |
| Test command | No | How to run tests (e.g., `dotnet test`). Auto-detect if not provided |

## Response Guidelines

- **Always identify the current xUnit version first.** State whether the project is on xUnit v2 (`xunit` 2.x) or xUnit v3 (`xunit.v3` / `xunit.v3.*`) before recommending changes. This grounds the migration advice -- some breaking-change steps only apply to one version.
- **Always preserve the current test platform.** If the project runs on VSTest, keep VSTest. If it runs on MTP (e.g., xUnit v3 native MTP, or `<UseMicrosoftTestingPlatformRunner>true</UseMicrosoftTestingPlatformRunner>`), keep MTP. Recommend `migrate-vstest-to-mtp` as a separate follow-up only if the user asks for it.
- **Explicitly communicate every judgement-call decision** before applying it -- otherwise the user cannot tell what changed semantically. In particular:
  - **Fixture scope changes** (Step 8): state the source scope (class / collection / assembly) and the target scope you chose, plus what gets shared and what gets serialized. A silent widening from collection to assembly is the most common way this migration regresses tests.
  - **Parallelization** (Step 11): state that **MSTest defaults to serial execution** (xUnit parallelizes classes by default), so an explicit `[assembly: Parallelize(...)]` is **required** to match xUnit's behaviour -- omitting it silently halves CI throughput.
  - **`Assert.Throws<T>` -> `Assert.ThrowsExactly<T>`** (Step 6): mention the exact-type-vs-any-derived semantic flip so reviewers know the assertion was deliberately renamed, not just translated.
- **Specific API mapping questions** (assertions, fixtures, output helper, etc.): jump to the relevant step. Do not run the full workflow.
- **Full migration requests**: follow the workflow end-to-end.
- **Focused fix requests** (specific compile error after a partial migration): address only that error using the mapping reference. Do not walk the full workflow.
- **Code samples**: show concrete before/after using the user's actual type/method names, not generic placeholders.

## Strategy

The conversion is mechanical for ~80% of code (attributes and simple assertions) and judgement-based for ~20% (collection fixtures, custom data attributes, exact-type-vs-derived exception assertions, parallelization semantics). Always do the mechanical pass first so build errors point you at the judgement areas.

## Mapping Reference

For the full attribute/assertion/fixture/lifecycle mapping tables -- including semantic traps (`Assert.Throws<T>` vs `Assert.ThrowsAny<T>`, `IClassFixture` vs `ICollectionFixture` scope), edge cases (`TheoryData<T...>`, `MemberType=`, custom `DataAttribute`, custom `FactAttribute`, `Record.Exception`), and copy-pasteable before/after snippets -- see [`references/mapping-cheatsheet.md`](references/mapping-cheatsheet.md). Load it whenever you need a specific xUnit -> MSTest equivalent.

For writing idiomatic MSTest code (modern assertion APIs, lifecycle patterns, data-driven conventions, `Assert.HasCount`/`IsEmpty`/`StartsWith`, etc.), see the `writing-mstest-tests` skill. **Do not re-derive idiomatic MSTest patterns here.** Apply this skill to *convert*; apply `writing-mstest-tests` to *polish*.

## Workflow

> **Commit strategy:** Commit after Step 2 (packages updated, builds broken), after Step 6 (attributes converted, asserts fixed), and after Step 8 (fixtures/lifecycle rewritten, tests pass). Commit before fixing follow-up cleanup so reviewers can bisect.

### Step 1: Assess the project

1. Locate every test project. Read `.csproj`, `Directory.Build.props`, `Directory.Packages.props`, and `global.json`.
2. Identify the **xUnit version**:
   - `xunit` 2.x (+ `xunit.assert` / `xunit.core` / `xunit.abstractions`) -> **xUnit v2**
   - `xunit.v3` / `xunit.v3.*` -> **xUnit v3**
3. Identify the **current test platform** (this dictates what to keep, not what to change) by invoking the `platform-detection` skill. The xUnit/MTP matrix is nuanced -- xunit.v3 inside Test Explorer is MTP by default unless opted out, while xunit.v3 inside `dotnet test` depends on the `xunit.v3.mtp-v*` packages -- so do not try to inline a shortcut here. Quick signals to feed into that skill: `xunit.runner.visualstudio` (v2) usually means VSTest; `xunit.v3.mtp-v*` / `xunit.v3.core.mtp-v*` packages or `YTest.MTP.XUnit2` (v2 MTP shim) usually mean MTP. `<UseMicrosoftTestingPlatformRunner>` only affects `dotnet run` and is **not** a reliable VSTest-vs-MTP signal on its own.
4. Verify the `TargetFramework` is supported by MSTest v4:
   - **Supported**: `net8.0`, `net9.0`, `net462`+, `netstandard2.0` (test library only), `uap10.0.16299`, `net8.0-windows10.0.18362.0` (WinUI), `net9.0-windows10.0.17763.0` (modern UWP).
   - **Unsupported**: .NET Core 3.1, `net5.0`-`net7.0`. **STOP** and ask the user to upgrade the TFM first, or migrate to MSTest v3 (then use `migrate-mstest-v3-to-v4` after a TFM bump).
5. Inventory high-risk patterns -- scan for these and flag them now so you can plan judgement steps later:
   - **Parallelization differences (Step 11)** -- xUnit parallelizes test classes by default; MSTest does not. This is the **single most common source of post-migration regressions**: tests that depended on isolation by parallel scheduling, on the lack of it, or on shared static state can pass differently. Decide the target parallelization model now -- do not leave it as the MSTest default by accident.
   - `ICollectionFixture<T>` / `[CollectionDefinition]` (scope concern -- see Step 8)
   - Custom `DataAttribute` / custom `FactAttribute` / custom `TheoryAttribute` subclasses (manual conversion to `ITestDataSource` / `TestMethodAttribute` -- see Step 5)
   - `Assert.Throws<T>` (xUnit semantics = exact type; maps to `Assert.ThrowsExactly<T>`, **not** `Assert.Throws<T>`)
   - `Record.Exception` / `Record.ExceptionAsync` (manual conversion)
   - `Assert.Raises*` / event assertions (no MSTest equivalent -- manual)
   - xUnit v3: `[assembly: CaptureConsole]` and other v3-only assembly attributes
6. **Inventory state shared between tests** -- static fields/properties, singletons, file paths, well-known ports, in-memory caches, database connection strings pointing at a single shared DB, environment variables. Whether parallelization is on or off, switching frameworks changes the *order* and *concurrency* in which these are touched. List them now so you can decide in Step 11 whether to enable parallelism, serialize specific classes with `[DoNotParallelize]`, or refactor the shared state.
7. Run a baseline build + test to record the current pass/fail count for parit

Related in Code Review