Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions plugins/dotnet-test/skills/migrate-xunit-to-mstest/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,15 @@ With the pin in `global.json`, the project line simplifies to `<Project Sdk="MST

When switching to `MSTest.Sdk`, also remove now-redundant properties: `<OutputType>Exe</OutputType>`, `<IsPackable>false</IsPackable>`, `<IsTestProject>true</IsTestProject>`, `<EnableMSTestRunner>`.

`MSTest.Sdk` also adds `Microsoft.VisualStudio.TestTools.UnitTesting` as an **implicit global using**. Do **not** add `<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />` to the project (it's noise) and skip the per-file `using Microsoft.VisualStudio.TestTools.UnitTesting;` in Step 4 -- you only need it for projects on Option A (the `MSTest` metapackage).

### Step 3: Update project configuration

1. **Preserve the runner.** Confirm the platform decision from Step 1 still holds after Step 2. Common mistakes:
- Switching to `MSTest.Sdk` without `UseVSTest=true` silently flips a VSTest project to MTP. Add `<UseVSTest>true</UseVSTest>` to the project (the SDK pulls in `Microsoft.NET.Test.Sdk` automatically -- no manual `PackageReference` needed).
- `<UseMicrosoftTestingPlatformRunner>true</UseMicrosoftTestingPlatformRunner>` only affects the `dotnet run` entry point and is **not** a runner switch in Test Explorer or `dotnet test`. Do not infer the platform from this property in either direction -- defer to the `platform-detection` skill (see Step 1).
2. Delete `xunit.runner.json` and port any settings you need (parallelization, `[CollectionBehavior]`, `appDomain`) per Step 11's "xunit.runner.json -> MSTest" sub-table. The settings have no direct MSBuild-property mapping.
3. Remove `using Xunit;` and `using Xunit.Abstractions;` from C# files (the rewriter will add `using Microsoft.VisualStudio.TestTools.UnitTesting;` instead in Step 4).
3. Remove `using Xunit;` and `using Xunit.Abstractions;` from C# files. For **Option A** (`MSTest` metapackage), add `using Microsoft.VisualStudio.TestTools.UnitTesting;` per file (Step 4 covers this alongside the other rewrites). For **Option B** (`MSTest.Sdk`), skip the per-file using -- the SDK provides it as an implicit global using.

Comment thread
Evangelink marked this conversation as resolved.
### Step 4: Convert test classes and methods

Expand All @@ -166,7 +168,7 @@ Apply these rewrites to every C# test file. Class-level first, then method-level

- Add `[TestClass]` to every class that contained xUnit `[Fact]`/`[Theory]` methods (xUnit had no class-level requirement).
- **Preserve the original class hierarchy.** xUnit projects often use base/derived test classes (shared setup, helper assertions, generic base fixtures); marking classes `sealed` would break that pattern. Sealing is an optional follow-up handled by `writing-mstest-tests`, not part of the mechanical migration.
- Replace `using Xunit;` / `using Xunit.Abstractions;` with `using Microsoft.VisualStudio.TestTools.UnitTesting;`.
- Replace `using Xunit;` / `using Xunit.Abstractions;` with `using Microsoft.VisualStudio.TestTools.UnitTesting;`. **On Option B (`MSTest.Sdk`), skip adding the MSTest using** -- the SDK provides it as an implicit global using, so just remove the `using Xunit;` / `using Xunit.Abstractions;` lines (Step 2 and Step 3 cover this).

**Methods:**

Expand Down Expand Up @@ -213,7 +215,9 @@ Most common cases inline. For the full table including string/collection/type/nu
| `Assert.Throws<T>(() => ...)` | **`Assert.ThrowsExactly<T>(() => ...)`** (see trap below) |
| `Assert.ThrowsAny<T>(() => ...)` | **`Assert.Throws<T>(() => ...)`** |
| `await Assert.ThrowsAsync<T>(...)` | `await Assert.ThrowsExactlyAsync<T>(...)` |
| `Assert.IsType<T>(x)` / `Assert.IsAssignableFrom<T>(x)` | `Assert.IsInstanceOfType<T>(x)` (MSTest v4 returns the typed value) |
| `Assert.IsType<T>(x)` (exact-type check, returns `T`) | `Assert.IsExactInstanceOfType<T>(x)` (MSTest 4.1+, returns `T`) -- **not** `Assert.IsInstanceOfType<T>`, which is assignable/is-a and silently weakens the assertion |
| `Assert.IsNotType<T>(x)` (exact-type check) | `Assert.IsNotExactInstanceOfType<T>(x)` (MSTest 4.1+) |
| `Assert.IsAssignableFrom<T>(x)` | `Assert.IsInstanceOfType<T>(x)` (MSTest v4 returns the typed value) |
| `Assert.Empty(coll)` / `Assert.NotEmpty(coll)` | `Assert.IsEmpty(coll)` / `Assert.IsNotEmpty(coll)` |
| `Assert.Single(coll)` | `var item = Assert.ContainsSingle(coll);` |
| `Assert.Contains(item, coll)` / `Assert.DoesNotContain(...)` | Same -- `Assert.Contains` / `Assert.DoesNotContain` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,12 @@ Target framework throughout: **MSTest v4** (the few v3-only spellings are explic

| xUnit | MSTest |
|---|---|
| `Assert.IsType<T>(x)` (exact type, returns `T`) | MSTest v4: `var t = Assert.IsInstanceOfType<T>(x);` (semantically *assignable*, not exact); for exact-type, follow with `Assert.AreEqual(typeof(T), x.GetType())` |
| `Assert.IsNotType<T>(x)` (exact type) | `Assert.IsNotInstanceOfType<T>(x);` plus `Assert.AreNotEqual(typeof(T), x.GetType())` if exact-type matters |
| `Assert.IsAssignableFrom<T>(x)` | `Assert.IsInstanceOfType<T>(x)` -- semantically equivalent |
| `Assert.IsType<T>(x)` (exact type, returns `T`) | `var t = Assert.IsExactInstanceOfType<T>(x);` (MSTest 4.1+; returns the typed value, exact match) |
| `Assert.IsNotType<T>(x)` (exact type) | `Assert.IsNotExactInstanceOfType<T>(x);` (MSTest 4.1+) |
| `Assert.IsAssignableFrom<T>(x)` | `Assert.IsInstanceOfType<T>(x)` -- semantically equivalent (assignable-from check) |

> MSTest 4.1+ adds `Assert.IsExactInstanceOfType<T>(x)` -- the proper equivalent of xUnit's exact-type `Assert.IsType<T>` (returns `T`, single call). On pre-4.1 MSTest, fall back to `var t = Assert.IsInstanceOfType<T>(x); Assert.AreEqual(typeof(T), x.GetType());`. `Assert.IsInstanceOfType<T>(x)` on its own is **assignable-only** (= xUnit `Assert.IsAssignableFrom<T>`); silently mapping `IsType<T>` to it loses exact-type semantics.
>
> MSTest v4's `Assert.IsInstanceOfType<T>(x)` returns the typed value (no out param). MSTest v3 uses `Assert.IsInstanceOfType<T>(x, out var typed)`.

### 3.4 Numeric / comparison
Expand Down Expand Up @@ -150,8 +152,8 @@ Target framework throughout: **MSTest v4** (the few v3-only spellings are explic
| `Assert.Single(collection, predicate)` | `var item = Assert.ContainsSingle(collection.Where(predicate));` |
| `Assert.Collection(items, e1 => ..., e2 => ...)` | **Manual** -- assert count, then per-element. No idiomatic MSTest equivalent |
| `Assert.All(items, x => assertion(x))` | **Manual** -- `foreach (var x in items) assertion(x);` |
| `Assert.Equal(expected, actual)` on `IEnumerable<T>` (element-wise) | `CollectionAssert.AreEqual(expected.ToList(), actual.ToList())` (`IList` required) |
| `Assert.Equal(expected, actual, comparer)` on collections | `CollectionAssert.AreEqual(expected.ToList(), actual.ToList(), comparer)` |
| `Assert.Equal(expected, actual)` on `IEnumerable<T>` (element-wise) | `Assert.AreSequenceEqual(expected, actual)` (MSTest 4.3+); pre-4.3: `CollectionAssert.AreEqual(expected.ToList(), actual.ToList())` (`IList` required). Plain `Assert.AreEqual` does **not** compare element-wise (MSTEST0065). |
| `Assert.Equal(expected, actual, comparer)` on collections | `Assert.AreSequenceEqual(expected, actual, comparer)` (MSTest 4.3+); pre-4.3: `CollectionAssert.AreEqual(expected.ToList(), actual.ToList(), comparer)` |
| `Assert.Distinct(collection)` | **Manual** -- `Assert.AreEqual(collection.Count, collection.Distinct().Count())` |
| `Assert.Superset(expected, actual)` | **Manual** -- `Assert.IsTrue(expected.IsSubsetOf(actual))` if both are `HashSet<T>` |

Expand Down Expand Up @@ -365,6 +367,13 @@ Prefer pinning the `MSTest.Sdk` version in `global.json` (especially in solution

With the pin in `global.json`, the project line simplifies to `<Project Sdk="MSTest.Sdk">`.

`MSTest.Sdk` adds `Microsoft.VisualStudio.TestTools.UnitTesting` as an **implicit global using**, so:

- **Do not** add `<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />` to the project file -- it's redundant noise.
- **Do not** add `using Microsoft.VisualStudio.TestTools.UnitTesting;` to each test file -- it's already in scope.

(Option A -- the `MSTest` metapackage -- does not bring the global using; per-file `using Microsoft.VisualStudio.TestTools.UnitTesting;` is still required there.)

## 10. Companion / extension libraries

| xUnit companion | MSTest equivalent |
Expand All @@ -374,6 +383,6 @@ With the pin in `global.json`, the project line simplifies to `<Project Sdk="MST
| `Xunit.StaFact` (`[StaFact]`, `[WpfFact]`) | No equivalent -- manual STA thread or flag for review |
| `Xunit.Priority` (`[TestCaseOrderer]`) | MSTest ordering is different -- flag for manual |
| `Verify.Xunit` | `Verify.MSTest` (swap the package; same usage) |
| `FluentAssertions` / `Shouldly` / `AwesomeAssertions` | Keep -- assertion libraries are framework-agnostic |
| `FluentAssertions` / `Shouldly` / `AwesomeAssertions` | Keep -- assertion libraries are framework-agnostic. (`AwesomeAssertions` is a fork of `FluentAssertions` and ships in the `FluentAssertions` namespace for API compat -- no source changes needed.) |
| `Moq` / `NSubstitute` / `FakeItEasy` | Keep -- mocking libraries are framework-agnostic |
| `AutoFixture.Xunit2` (`[AutoData]`) | `AutoFixture` core works, but the auto-data attribute integration requires the xUnit-specific package -- flag for manual |
Loading