Fix build errors and breaking changes after upgrading MSTest from v3 to v4, or plan a complete MSTest v3-to-v4 migration. Use when user says "upgrade to MSTest v4", "MSTest 4 migration", "MSTest v4 breaking changes", "tests don't compile after upgrading MSTest", or has errors CS0507, CS0103, CS1061, CS1615 after updating MSTest packages from 3.x to 4.x. USE FOR: Execute to ExecuteAsync, CallerInfo constructor on TestMethodAttribute, sealed custom attributes, ClassCleanupBehavior removal, TestContext.Properties Contains to ContainsKey, Assert.ThrowsException to ThrowsExactly, Assert.IsInstanceOfType out parameter removal, ExpectedExceptionAttribute removal, TestTimeout enum removal, [TestMethod("name")] to DisplayName syntax, TreatDiscoveryWarningsAsErrors, TestContext.TestName in ClassInitialize, MSTest.Sdk MTP changes, dropped TFMs (net6.0/net7.0 to net8.0+). DO NOT USE FOR: migrating from MSTest v1/v2 to v3 (use migrate-mstest-v1v2-to-v3 first), migrating between test frameworks, or general .NET upgrades.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
Fix build errors and breaking changes after upgrading MSTest from v3 to v4, or plan a complete MSTest v3-to-v4 migration. Use when user says "upgrade to MSTest v4", "MSTest 4 migration", "MSTest v4 breaking changes", "tests don't compile after upgrading MSTest", or has errors CS0507, CS0103, CS1061, CS1615 after updating MSTest packages from 3.x to 4.x. USE FOR: Execute to ExecuteAsync, CallerInfo constructor on TestMethodAttribute, sealed custom attributes, ClassCleanupBehavior removal, TestContext.Properties Contains to ContainsKey, Assert.ThrowsException to ThrowsExactly, Assert.IsInstanceOfType out parameter removal, ExpectedExceptionAttribute removal, TestTimeout enum removal, [TestMethod("name")] to DisplayName syntax, TreatDiscoveryWarningsAsErrors, TestContext.TestName in ClassInitialize, MSTest.Sdk MTP changes, dropped TFMs (net6.0/net7.0 to net8.0+). DO NOT USE FOR: migrating from MSTest v1/v2 to v3 (use migrate-mstest-v1v2-to-v3 first), migrating between test frameworks, or general .NET upgrades.
Migrate a test project from MSTest v3 to MSTest v4. The outcome is a project using MSTest v4 that builds cleanly, passes tests, and accounts for every source-incompatible and behavioral change. MSTest v4 is not binary compatible with MSTest v3 -- any library compiled against v3 must be recompiled against v4.
When to Use
Upgrading MSTest.TestFramework, MSTest.TestAdapter, or MSTest metapackage from 3.x to 4.x
Upgrading MSTest.Sdk from 3.x to 4.x
Fixing build errors after updating to MSTest v4 packages
Resolving behavioral changes in test execution after upgrading to MSTest v4
Updating custom TestMethodAttribute or ConditionBaseAttribute implementations for v4
When Not to Use
The project already uses MSTest v4 and builds cleanly -- migration is done
Upgrading from MSTest v1 or v2 -- use migrate-mstest-v1v2-to-v3 first, then return here
The project does not use MSTest
Migrating between test frameworks (e.g., MSTest to xUnit or NUnit)
Inputs
Input
Required
Description
Project or solution path
Yes
The .csproj, .sln, or .slnx entry point containing MSTest test projects
Build command
No
How to build (e.g., dotnet build, a repo build script). 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 version first: Before recommending any migration steps, explicitly state the current MSTest version detected in the project (e.g., "Your project uses MSTest v3 (3.8.0)"). This confirms you've read the project files and grounds the migration advice.
Focused fix requests (user has specific compilation errors after upgrading): Address only the relevant breaking changes from Step 3. Always provide concrete fixed code using the user's actual types and method names — show a complete, copy-pasteable code snippet, not just a description of what to change. For custom TestMethodAttribute subclasses, show the full fixed class including CallerInfo propagation to the base constructor. Mention any related analyzer that could have caught this earlier (e.g., MSTEST0006 for ExpectedException). Do not walk through the entire migration workflow.
"What to expect" questions (user asks about breaking changes before upgrading): Present ALL major breaking changes from the Step 3 quick-lookup table -- not just the ones visible in the current code. For each, provide a one-line fix summary. Also mention key behavioral changes from Step 4 (especially TestCase.Id history impact and TreatDiscoveryWarningsAsErrors default). If project code is available, highlight which changes apply directly.
Full migration requests (user wants complete migration): Follow the complete workflow below.
Behavioral/runtime symptom reports (user describes test execution differences without build errors): Match described symptoms to the behavioral changes table in Step 4. Provide targeted, symptom-specific advice. Mention other behavioral changes the user should watch for. Do not walk through source breaking changes unless the user also has build errors.
CI/test-discovery issues (tests not discovered, vstest.console stopped working, CI pipeline failures after upgrading): Focus on 4.5 (MSTest.Sdk defaults to MTP mode, which does not include Microsoft.NET.Test.Sdk -- needed for vstest.console) and 4.4 (TreatDiscoveryWarningsAsErrors). Explain the root cause clearly and give both fix options (add Microsoft.NET.Test.Sdk package or switch to dotnet test). Do not walk through the full migration workflow.
Explanatory questions (user asks "is this a known change?", "what else should I watch out for?"): Explain the relevant changes and advise. Mention related changes the user might encounter next. Do not prescribe a full migration procedure.
Workflow
Commit strategy: Commit at each logical boundary -- after updating packages (Step 2), after resolving source breaking changes (Step 3), after addressing behavioral changes (Step 4). This keeps each commit focused and reviewable.
Step 1: Assess the project
Identify the current MSTest version by checking package references for MSTest, MSTest.TestFramework, MSTest.TestAdapter, or MSTest.Sdk in .csproj, Directory.Build.props, or Directory.Packages.props.
Confirm the project is on MSTest v3 (3.x). If on v1 or v2, use migrate-mstest-v1v2-to-v3 first.
Check target framework(s) -- MSTest v4 drops support for .NET Core 3.1 through .NET 7. Supported target frameworks are: net8.0, net9.0, net462 (.NET Framework 4.6.2+), uap10.0.16299 (UWP), net9.0-windows10.0.17763.0 (modern UWP), and net8.0-windows10.0.18362.0 (WinUI).
Check for custom TestMethodAttribute subclasses -- these require changes in v4.
Check for usages of ExpectedExceptionAttribute -- removed in v4 (deprecated since v3 with analyzer MSTEST0006).
Check for usages of Assert.ThrowsException (deprecated) -- removed in v4.
Run a clean build to establish a baseline of existing errors/warnings.
Run dotnet restore, then dotnet build. Collect all errors for Step 3.
Step 3: Resolve source breaking changes
Work through compilation errors systematically. Use this quick-lookup table to identify all applicable changes, then apply each fix:
Error / Pattern in code
Breaking change
Fix
Custom TestMethodAttribute overrides Execute
Execute removed
Change to ExecuteAsync returning Task<TestResult[]> (3.1)
[TestMethod("name")] or custom attribute constructor
CallerInfo params added
Use DisplayName = "name" named param; propagate CallerInfo in subclasses (3.2)
ClassCleanupBehavior.EndOfClass
Enum removed
Remove argument: just [ClassCleanup] (3.3)
TestContext.Properties.Contains("key")
Properties is IDictionary<string, object>
Change to ContainsKey("key") (3.4)
[Timeout(TestTimeout.Infinite)]
TestTimeout enum removed
Replace with [Timeout(int.MaxValue)] (3.5)
TestContext.ManagedType
Property removed
Use FullyQualifiedTestClassName (3.6)
Assert.AreEqual(a, b, "msg {0}", arg)
Message+params overloads removed
Use string interpolation: $"msg {arg}" (3.7)
Assert.ThrowsException<T>(...)
Renamed
Replace with Assert.ThrowsExactly<T>(...) or Assert.Throws<T>(...) (3.7)
Assert.IsInstanceOfType<T>(obj, out var t)
Out parameter removed
Use var t = Assert.IsInstanceOfType<T>(obj) (3.7)
[ExpectedException(typeof(T))]
Attribute removed
Move assertion into test body: Assert.ThrowsExactly<T>(() => ...) (3.8)
Project targets net5.0, net6.0, or net7.0
TFM dropped
Change to net8.0 or net9.0 (3.9)
Important: Scan the entire project for ALL patterns above before starting fixes. Multiple breaking changes often coexist in the same project.
3.1 TestMethodAttribute.Execute -> ExecuteAsync
If you have custom TestMethodAttribute subclasses that override Execute, change to ExecuteAsync. This change was made because the v3 synchronous Execute API caused deadlocks when test code used async/await internally -- the synchronous wrapper would block the thread while the async operation needed that same thread to complete.
If you use [TestMethodAttribute("Custom display name")], switch to the named parameter syntax:
// Before (v3)
[TestMethodAttribute("Custom display name")]
// After (v4)
[TestMethodAttribute(DisplayName = "Custom display name")]
3.3 ClassCleanupBehavior enum removed
The ClassCleanupBehavior enum is removed. In v3, this enum controlled whether class cleanup ran at end of class (EndOfClass) or end of assembly (EndOfAssembly). In v4, class cleanup always runs at end of class. Remove the enum argument:
// Before (v3)
[ClassCleanup(ClassCleanupBehavior.EndOfClass)]
publicstaticvoidClassCleanup(TestContext testContext) { }
// After (v4)
[ClassCleanup]
publicstaticvoidClassCleanup(TestContext testContext) { }
If you previously used ClassCleanupBehavior.EndOfAssembly, move that cleanup logic to an [AssemblyCleanup] method instead.
3.4 TestContext.Properties type change
TestContext.Properties changed from IDictionary to IDictionary<string, object>. Update any Contains calls to ContainsKey:
// Before (v3)
testContext.Properties.Contains("key");
// After (v4)
testContext.Properties.ContainsKey("key");
3.5 TestTimeout enum removed
The TestTimeout enum (with only TestTimeout.Infinite) is removed. Replace with int.MaxValue:
// Before (v3)
[Timeout(TestTimeout.Infinite)]
// After (v4)
[Timeout(int.MaxValue)]
3.6 TestContext.ManagedType removed
The TestContext.ManagedType property is removed. Use TestContext.FullyQualifiedTestClassName instead.
3.7 Assert API signature changes
Message + params removed: Assert methods that accepted both message and object[] parameters now accept only message. Use string interpolation instead of format strings:
// Before (v3)
Assert.AreEqual(expected, actual, "Expected {0} but got {1}", expected, actual);
// After (v4)
Assert.AreEqual(expected, actual, $"Expected {expected} but got {actual}");
Assert.ThrowsException renamed: The Assert.ThrowsException APIs are renamed. Use Assert.ThrowsExactly (strict type match) or Assert.Throws (accepts derived exception types):
// Before (v3)
Assert.ThrowsException<InvalidOperationException>(() => DoSomething());
// After (v4) -- exact type match (same behavior as old ThrowsException)
Assert.ThrowsExactly<InvalidOperationException>(() => DoSomething());
// After (v4) -- also catches derived exception types
Assert.Throws<InvalidOperationException>(() => DoSomething());
Assert.IsInstanceOfType out parameter changed: Assert.IsInstanceOfType<T>(x, out var t) changes to var t = Assert.IsInstanceOfType<T>(x):
// Before (v3)
Assert.IsInstanceOfType<MyType>(obj, outvar typed);
// After (v4)var typed = Assert.IsInstanceOfType<MyType>(obj);
Assert.AreEqual for IEquatable<T> removed: If you get generic type inference errors, explicitly specify the type argument as object.
3.8 ExpectedExceptionAttribute removed
The [ExpectedException] attribute is removed in v4. In MSTest 3.2, the MSTEST0006 analyzer was introduced to flag [ExpectedException] usage and suggest migrating to Assert.ThrowsExactly while still on v3 (a non-breaking change). In v4, the attribute is gone entirely. Migrate to Assert.ThrowsExactly:
// Before (v3)
[ExpectedException(typeof(InvalidOperationException))]
[TestMethod]
publicvoidTestMethod()
{
MyCall();
}
// After (v4)
[TestMethod]
publicvoidTestMethod()
{
Assert.ThrowsExactly<InvalidOperationException>(() => MyCall());
}
When the test has setup code before the throwing call, wrap only the throwing call in the lambda -- keep Arrange/Act separation clear:
// Before (v3)
[ExpectedException(typeof(ArgumentNullException))]
[TestMethod]
publicvoidValidate_NullInput_Throws()
{
var service = new ValidationService();
service.Validate(null); // throws here
}
// After (v4)
[TestMethod]
publicvoidValidate_NullInput_Throws()
{
var service = new ValidationService();
Assert.ThrowsExactly<ArgumentNullException>(() => service.Validate(null));
}
For async test methods, use Assert.ThrowsExactlyAsync:
If [ExpectedException] used the AllowDerivedTypes property, use Assert.ThrowsAsync<T> (base type matching) instead of Assert.ThrowsExactlyAsync<T> (exact type matching).
3.9 Dropped target frameworks
MSTest v4 supports: net8.0, net9.0, net462 (.NET Framework 4.6.2+), uap10.0.16299 (UWP), net9.0-windows10.0.17763.0 (modern UWP), and net8.0-windows10.0.18362.0 (WinUI). All other frameworks are dropped -- including net5.0, net6.0, net7.0, and netcoreapp3.1.
If the test project targets an unsupported framework, update TargetFramework:
<!-- Before --><TargetFramework>net6.0</TargetFramework><!-- After --><TargetFramework>net8.0</TargetFramework>
3.10 Unfolding strategy moved to TestMethodAttribute
The UnfoldingStrategy property (introduced in MSTest 3.7) has moved from individual data source attributes (DataRowAttribute, DynamicDataAttribute) to TestMethodAttribute.
3.11 ConditionBaseAttribute.ShouldRun renamed
The ConditionBaseAttribute.ShouldRun property is renamed to IsConditionMet.
3.12 Internal/removed types
Several types previously public are now internal or removed:
ITestMethod in PlatformServices assembly (the one in TestFramework is unchanged)
If your code references any of these, find alternative approaches or remove the dependency.
Step 4: Address behavioral changes
These changes won't cause build errors but may affect test runtime behavior.
Symptom
Cause
Fix
Tests show as new in Azure DevOps / test history lost
TestCase.Id generation changed (4.3)
No code fix; history will re-baseline
TestContext.TestName throws in [ClassInitialize]
v4 enforces lifecycle scope (4.2)
Move access to [TestInitialize] or test methods
Tests not discovered / discovery failures
TreatDiscoveryWarningsAsErrors now true (4.4)
Fix warnings, or set to false in .runsettings
Tests hang that didn't before
AppDomain disabled by default (4.1)
Set DisableAppDomain to false in .runsettings RunConfiguration
vstest.console can't find tests with MSTest.Sdk
MSTest.Sdk defaults to MTP; Microsoft.NET.Test.Sdk only added in VSTest mode (4.5)
Add explicit package reference or switch to dotnet test
New warnings from analyzers
Analyzer severities upgraded (4.6)
Fix warnings or suppress in .editorconfig
4.1 DisableAppDomain defaults to true
AppDomains are disabled by default. On .NET Framework, when running inside testhost (the default for dotnet test and VS), MSTest re-enables AppDomains automatically. If you need to explicitly control AppDomain isolation, set it via .runsettings:
MSTest v4 now throws when accessing test-specific properties in the wrong lifecycle stage:
TestContext.FullyQualifiedTestClassName -- cannot be accessed in [AssemblyInitialize]
TestContext.TestName -- cannot be accessed in [AssemblyInitialize] or [ClassInitialize]
Fix: Move any code that accesses TestContext.TestName from [ClassInitialize] to [TestInitialize] or individual test methods, where per-test context is available. Do not replace TestName with FullyQualifiedTestClassName as a workaround -- they have different semantics.
4.3 TestCase.Id generation changed
The generation algorithm for TestCase.Id has changed to fix long-standing bugs. This may affect Azure DevOps test result tracking (e.g., test failure tracking over time). There is no code fix needed, but be aware of test result history discontinuity.
4.4 TreatDiscoveryWarningsAsErrors defaults to true
v4 uses stricter defaults. Discovery warnings are now treated as errors, which means tests that previously ran despite discovery issues may now fail entirely. If you see unexpected test failures after upgrading (not build errors, but tests not being discovered), check for discovery warnings. To restore v3 behavior while you investigate:
Recommended: Fix the underlying discovery warnings rather than suppressing this setting.
4.5 MSTest.Sdk and vstest.console compatibility
MSTest.Sdk defaults to Microsoft.Testing.Platform (MTP) mode. In MTP mode, MSTest.Sdk does not add a reference to Microsoft.NET.Test.Sdk -- it only adds it in VSTest mode. This is not a v4-specific change; it applies to MSTest.Sdk v3 as well. Without Microsoft.NET.Test.Sdk, vstest.console cannot discover or run tests and will silently find zero tests. This commonly surfaces during migration when a CI pipeline uses vstest.console but the project uses MSTest.Sdk in its default MTP mode.
Option A -- Switch to VSTest mode: Set the UseVSTest property. MSTest.Sdk will then automatically add Microsoft.NET.Test.Sdk:
Option B -- Switch CI to dotnet test: Replace vstest.console invocations in your CI pipeline with dotnet test. This works natively with MTP and is the recommended long-term approach for MSTest.Sdk projects.
If you need VSTest during a transition period, Option A works without changing CI pipelines.
4.6 Analyzer severity changes
Multiple analyzers have been upgraded from Info to Warning by default: