Use this skill before answering or editing whenever an MSTest v1/v2 project is being upgraded or repaired for v3.
NUnit -> MSTest Migration
Convert .NET tests from NUnit 3/4 to MSTest v4 while preserving VSTest or MTP. Use for replacing NUnit/NUnit3TestAdapter packages, Test/TestCase/ TestCaseSource/Values attributes, constraint assertions, SetUp/TearDown, OneTimeSetUp, SetUpFixture, FixtureLifeCycle, TestContext, categories, retries, timeouts, and NUnit parallelization. Also use when a "convert NUnit to MSTest" request may already be migrated: inspect and report the no-op. Do not use for NUnit version upgrades, xUnit/TUnit conversion, MSTest upgrades, or runner-only VSTest-to-MTP migration.
Workflow
1. Establish the baseline
- Batch-read test projects, central package files,
global.json,.runsettings,testconfig.json, and NUnit configuration. - Detect NUnit from
NUnit,NUnit3TestAdapter,NUnit.Analyzers,NUnit.Framework,NUnit.Framework.Legacy, andusing NUnit.Framework. - State whether the source is NUnit 3 or 4 from the resolved package version.
- Detect VSTest or MTP and preserve it. Use
platform-detectiononly when ambiguous. - Record target frameworks and stop if MSTest v4 does not support them.
- Inventory high-risk constructs:
- [TestFixture(...)], [TestFixtureSource], [FixtureLifeCycle], constructors with parameters - [TestCase], [TestCaseSource], TestCaseData, [Values], [Range], [Random], [Sequential], [Pairwise], custom data attributes - [Theory], [Datapoint], [DatapointSource], automatic bool/enum datapoints, Assume.That - [OneTimeSetUp], [OneTimeTearDown], [SetUpFixture], inheritance-based setup - Assert.That, Assert.Multiple, Assert.Throws, Assert.Catch, collection constraints - [Parallelizable], [NonParallelizable], [LevelOfParallelism], [Order], [SingleThreaded] - [Apartment], [RequiresThread], [CancelAfter], [Timeout] - [Culture], [Platform], [SetCulture], [SetUICulture] - [Explicit], [Repeat], [Retry], [MaxTime], categories, properties, [TestOf] - [DefaultFloatingPointTolerance], [NonTestAssembly], and deprecated fixture lifecycle aliases
2. Replace packages without switching runners
Remove NUnit-specific packages being replaced, including NUnit, NUnit3TestAdapter, NUnit.Analyzers, NUnit console runner packages, and NUnit-specific MTP adapters.
Default to the current stable MSTest v4 metapackage resolved from the configured package source:
<PackageReference Include="MSTest" Version="4.4.0" />
The pin is illustrative for the current release; during a real migration resolve and pin the current stable version. Preserve an explicit Microsoft.NET.Test.Sdk dependency for VSTest when the source project owns one. When preserving MTP, prefer MSTest.Sdk; otherwise use EnableMSTestRunner=true and OutputType=Exe.
Do not change TargetFramework. Remove NUnit .runsettings adapter settings only after translating relevant behavior.
3. Perform the mechanical conversion
| NUnit | MSTest | |---|---| | [TestFixture] or fixture with tests | [TestClass] | | [Test] | [TestMethod] | | [TestCase(...)] | [TestMethod] + [DataRow(...)] | | [TestCaseSource(nameof(Cases))] | [TestMethod] + [DynamicData(nameof(Cases))] | | [SetUp] / [TearDown] | [TestInitialize] / [TestCleanup] | | [OneTimeSetUp] / [OneTimeTearDown] | static [ClassInitialize] / [ClassCleanup] | | [Category(value)] | [TestCategory(value)] | | [Property(key, value)] | [TestProperty(key, value)] | | [Ignore("reason")] | [Ignore("reason")] plus [TestMethod] | | [Timeout(ms)] | [Timeout(ms)] plus [TestMethod] | | [Retry(n)] | [Retry(n - 1)] plus [TestMethod] when n > 1; remove it when n = 1 |
Remove using NUnit.Framework; and using NUnit.Framework.Legacy;. Add using Microsoft.VisualStudio.TestTools.UnitTesting; when using the metapackage; MSTest.Sdk supplies an implicit global using.
Do not mechanically seal classes or flatten inherited setup methods.
4. Resolve semantic mappings
Load the mapping cheatsheet for every high-risk construct found in Step 1. These rules are mandatory:
- NUnit's default
LifeCycle.SingleInstancediffers from MSTest's per-test class instances. Preserve class-shared fields explicitly. [OneTimeSetUp]and[OneTimeTearDown]become static MSTest methods. Move any instance state they use to a static holder rather than merely addingstatic.- NUnit
Assert.Throws<T>andThrows.TypeOf<T>require an exact exception type and map toAssert.ThrowsExactly<T>. NUnitAssert.Catch<T>andThrows.InstanceOf<T>permit derived types and map toAssert.Throws<T>. - NUnit
Is.TypeOf<T>maps toAssert.IsExactInstanceOfType<T>;Is.InstanceOf<T>maps toAssert.IsInstanceOfType<T>. - NUnit equality constraints compare sequences element-by-element. Use
Assert.AreSequenceEqualon MSTest 4.3+ orCollectionAssert.AreEqualwith materialized lists; never use reference-basedAssert.AreEqualfor sequences. TestCase(ExpectedResult=...)andTestCaseData.Returns(...)require rewriting the target test to assert the expected result.- Preserve per-row names, categories, and ignores by returning
TestDataRow<T>fromDynamicDatawhen needed. NUnit row properties have no directTestDataRow<T>equivalent and require a custom data source or an explicit metadata decision. - MSTest 4.4 combinatorial attributes can map NUnit
[Combinatorial],[Values],[Range], and[Random]; addusing Microsoft.VisualStudio.TestTools.UnitTesting.Combinatorial;. Constructor semantics differ, so translate NUnit ranges and random bounds explicitly.[Sequential]still requires explicit rows or a custom data source. - NUnit
[Pairwise]has no MSTest built-in. Preserve the exact generated rows inDynamicDataor use a verified pairwiseITestDataSource; full Cartesian combinations change test counts and execution cost. - Convert
[Theory],[Datapoint], and[DatapointSource]into explicitDynamicDataor combinatorial sources. Preserve automatic bool/enum values and the rule that a theory fails when every row violates its assumptions. [Explicit],[Repeat], parameterized fixtures, fixture sources, andAssert.Multiplehave no behavior-identical mechanical mapping. Rewrite deliberately or report manual follow-up; never approximate silently.- NUnit
[Retry(n)]counts the initial attempt, while MSTest[Retry(n)]counts retries after the initial attempt. Subtract one and review NUnitRetryExceptionsfilters separately. [CancelAfter(ms)]maps to[Timeout(ms, CooperativeCancellation = true)]. Under VSTest, remove NUnit's injectedCancellationTokenparameter and use an injected or property-basedTestContext.CancellationToken; VSTest otherwise treats it as missing data. Retain a method token only when the preserved runner is proven to support injection. Expand fixture-level defaults to every affected test and lifecycle method.[Apartment(ApartmentState.STA)]maps to[STATestClass]or[STATestMethod]. SetUseSTASynchronizationContext = truewhen async continuations must remain on the STA thread. MTA is the MSTest default.[RequiresThread]and[SingleThreaded]guarantee thread identity that MSTest attributes do not generally preserve; use a custom executor or report manual follow-up.- Map NUnit platform and culture gates to
OSCondition,ArchitectureCondition, or aMemberConditionhelper. MapSetCultureandSetUICultureto setup/cleanup that saves and restores the original culture. - Expand
[DefaultFloatingPointTolerance]into explicit deltas on every affected assertion. Preserve method-over-fixture-over-assembly precedence.
5. Preserve lifecycle and namespace setup
[SetUp]and[TearDown]remain per-test lifecycle methods.- For NUnit's default single fixture instance, audit every mutable instance field. If tests depend on sharing, use static state created by
[ClassInitialize]and released by[ClassCleanup]. [FixtureLifeCycle(LifeCycle.InstancePerTestCase)]already matches MSTest class instantiation; remove the attribute and keep per-test instance state.[SetUpFixture]is namespace-scoped in NUnit. MSTest assembly initialization is assembly-wide. Use[AssemblyInitialize]only when the NUnit setup already covers the whole assembly; otherwise move setup into the affected classes or a shared helper without widening scope.- Preserve setup/cleanup inheritance order. Do not merge base and derived methods unless the resulting order is proven equivalent.
- Map NUnit
TestContext.WriteLineto an injected or property-based MSTestTestContext.WriteLine. Translate directory, test-name, and attachment APIs individually.
6. Preserve parallelization and ordering
NUnit and MSTest both run serially by default. Do not add parallelization for an unconfigured NUnit project.
When NUnit explicitly opts in:
- assembly
[Parallelizable(ParallelScope.Fixtures)]->[assembly: Parallelize(Workers = N, Scope = ExecutionScope.ClassLevel)] - fixture
[Parallelizable(ParallelScope.Children)]or method-level parallelism -> method-level MSTest parallelization only after confirming shared instance state is safe [NonParallelizable]->[DoNotParallelize]when parallelization is enabled[LevelOfParallelism(N)]->Workers = N- resource-specific serialization can use MSTest 4.4
[ResourceLock("key")]when it preserves a narrower lock than[DoNotParallelize]
NUnit [Order] is not general dependency semantics. Use MSTest 4.4 [DependsOn] only when the source truly expresses a prerequisite; otherwise remove ordering by making tests independent or report manual follow-up.
7. Verify parity
- Run tests with the same platform, filter, and configuration used for the baseline.
- Compare discovered, passed, failed, and skipped counts.
- Investigate every difference:
- missing rows -> DataRow, DynamicData, combinatorial, or row metadata conversion - changed exceptions/types -> exact-vs-derived mapping - state failures -> NUnit single-instance fixture semantics or setup order - concurrency failures -> Parallelize, DoNotParallelize, worker count, or resource locks - changed skips -> Ignore, Explicit, row-level ignore, or retry behavior
- Confirm no NUnit package, namespace, attribute, constraint, adapter setting, or custom NUnit extension remains unless documented for follow-up.
- Read back high-risk changed files and name the exact target APIs in the result.
Use this final response shape:
- Changed: files and exact high-risk mappings.
- Verified: final command and discovered/passed/failed/skipped counts.
- Preserved: target framework, test platform, fixture scope, and concurrency choice.
- Remaining: manual follow-up, or none.
Related skills
Use this skill before answering, planning, or editing any MSTest 3.x-to-4.x upgrade or post-upgrade failure.
Use this skill before answering, planning, or editing whenever .NET tests or CI are switching from VSTest to Microsoft.Testing.Platform (MTP), or an MTP migration behaves…