{
  "schema": "https://ai-atoms.com/schemas/skill-v1.json",
  "type": "skill",
  "id": "skill/writing-mstest-tests",
  "version": "1.0.1",
  "name": "writing-mstest-tests",
  "description": "ALWAYS USE when asked to fix, rewrite, update, improve, modernize, show corrected code for, or explain existing MSTest tests or MSTest-specific configuration. Use for \"review\" when corrected code or edits are wanted, even for one pasted assertion or passing tests with bad failure output. Covers expected/actual labels; generic Boolean, collection, string, numeric, null, identity, exception, hard-cast, and object[] checks; TestContext/lifecycle; timeout/cancellation; OS/CI conditions, retry, cleanup, parallelization, MSTest.Sdk project setup, and MSTESTxxxx. Honor the installed MSTest version. DO NOT USE to design new test cases (code-testing-agent), perform report-only audits, create project files rather than explain MSTest setup, run tests, migrate frameworks, or handle non-MSTest/non-.NET code.",
  "system_prompt_fragment": "# Writing MSTest Tests\n\nHelp users write effective, modern unit tests with MSTest 3.x/4.x using current APIs and best practices.\n\n## When to Use\n\n- User wants to write new MSTest unit tests\n- User wants to improve or modernize existing MSTest tests by implementing concrete fixes\n- User asks about MSTest assertion APIs, data-driven patterns, or test lifecycle\n- User asks to replace `Assert.IsTrue` with more specific assertions (collections, nulls, types, comparisons)\n- User asks to replace hard casts with type-checking assertions in tests\n- User needs help fixing a specific MSTest test bug or failing assertion\n- User asks to fix swapped `Assert.AreEqual` argument order (expected first, actual second)\n- User asks to convert `DynamicData` from `IEnumerable<object[]>` to ValueTuple-based data\n\n## When Not to Use\n\n- User needs a test quality audit, anti-pattern detection, or flaky-test investigation (use `test-anti-patterns`)\n- User needs to run or execute tests (use the `run-tests` skill)\n- User needs to upgrade from MSTest v1/v2 to v3 (use `migrate-mstest-v1v2-to-v3`)\n- User needs to upgrade from MSTest v3 to v4 (use `migrate-mstest-v3-to-v4`)\n- User needs CI/CD pipeline configuration\n- User is using xUnit, NUnit, or TUnit (not MSTest)\n\n## Inputs\n\n| Input | Required | Description |\n|-------|----------|-------------|\n| Code under test | No | The production code to be tested |\n| Existing test code | No | Current tests to fix, update, or modernize |\n| Test scenario description | No | What behavior the user wants to test |\n\n## Response Guidelines\n\n- **Specific API or pattern questions** (assertions, data-driven, lifecycle): Jump directly to the relevant workflow step. Do not follow the full workflow.\n- **Write new tests from scratch**: Follow the full workflow.\n- **Review and fix existing tests**: Fix only the issues present. Do not add unrelated improvements.\n\n## Workflow\n\n### Step 1: Determine project setup\n\nCheck the test project for MSTest version and configuration:\n\n- If using `MSTest.Sdk` (`<Sdk Name=\"MSTest.Sdk\">`): modern setup, all features available\n- If using `MSTest` metapackage: modern setup (MSTest 3.x+)\n- If using `MSTest.TestFramework` + `MSTest.TestAdapter`: check version for feature availability\n\nRecommend MSTest.Sdk or the MSTest metapackage for new projects:\n\n```xml\n<!-- Option 1: MSTest SDK (simplest, recommended for new projects) -->\n<Project Sdk=\"MSTest.Sdk\">\n  <PropertyGroup>\n    <TargetFramework>net9.0</TargetFramework>\n  </PropertyGroup>\n</Project>\n```\n\nWhen using `MSTest.Sdk`, put the version in `global.json` instead of the project file so all test projects get bumped together:\n\n```json\n{\n  \"msbuild-sdks\": {\n    \"MSTest.Sdk\": \"3.8.2\"\n  }\n}\n```\n\n```xml\n<!-- Option 2: MSTest metapackage -->\n<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <TargetFramework>net9.0</TargetFramework>\n  </PropertyGroup>\n  <ItemGroup>\n    <PackageReference Include=\"MSTest\" Version=\"3.8.2\" />\n  </ItemGroup>\n</Project>\n```\n\n### Step 2: Write test classes following conventions\n\nApply these structural conventions:\n\n- **Seal test classes** with `sealed` for performance and design clarity\n- Use `[TestClass]` on the class and `[TestMethod]` on test methods\n- Follow the **Arrange-Act-Assert** (AAA) pattern\n- Name tests using `MethodName_Scenario_ExpectedBehavior`\n- Use separate test projects with naming convention `[ProjectName].Tests`\n\n```csharp\n[TestClass]\npublic sealed class OrderServiceTests\n{\n    [TestMethod]\n    public void CalculateTotal_WithDiscount_ReturnsReducedPrice()\n    {\n        // Arrange\n        var service = new OrderService();\n        var order = new Order { Price = 100m, DiscountPercent = 10 };\n\n        // Act\n        var total = service.CalculateTotal(order);\n\n        // Assert\n        Assert.AreEqual(90m, total);\n    }\n}\n```\n\n### Step 3: Use modern assertion APIs\n\nPick the most specific assertion for each test scenario. More specific assertions produce better failure messages and make the test's intent clear:\n\n| What you are testing | Assertion |\n|---|---|\n| Two values are equal | `Assert.AreEqual(expected, actual)` |\n| Same object instance (reference identity) | `Assert.AreSame(expected, actual)` |\n| Value is null | `Assert.IsNull(value)` |\n| Value is not null | `Assert.IsNotNull(value)` |\n| Collection is empty | `Assert.IsEmpty(collection)` |\n| Collection is not empty | `Assert.IsNotEmpty(collection)` |\n| Collection has exactly N items | `Assert.HasCount(N, collection)` |\n| Collection contains an item | `Assert.Contains(item, collection)` |\n| Collection does not contain an item | `Assert.DoesNotContain(item, collection)` |\n| Object is a specific type | `Assert.IsInstanceOfType<T>(value)` |\n| Code throws an exception | `Assert.ThrowsExactly<T>(() => ...)` |\n\nPrefer `Assert` class methods over `StringAssert` or `CollectionAssert` where both exist.\n\n#### Equality, null, and reference checks\n\n```csharp\nAssert.AreEqual(expected, actual);      // Value equality\nAssert.AreSame(expected, actual);       // Reference equality -- same object instance\nAssert.IsNull(value);\nAssert.IsNotNull(value);\n```\n\n#### Exception testing -- use `Assert.Throws` instead of `[ExpectedException]`\n\n```csharp\n// Synchronous\nvar ex = Assert.ThrowsExactly<ArgumentNullException>(() => service.Process(null));\nAssert.AreEqual(\"input\", ex.ParamName);\n\n// Async\nvar ex = await Assert.ThrowsExactlyAsync<InvalidOperationException>(\n    async () => await service.ProcessAsync(null));\n```\n\n- `Assert.Throws<T>` matches `T` or any derived type\n- `Assert.ThrowsExactly<T>` matches only the exact type `T`\n\n#### Collection assertions\n\n```csharp\nAssert.Contains(expectedItem, collection);\nAssert.DoesNotContain(unexpectedItem, collection);\nvar single = Assert.ContainsSingle(collection);  // Returns the single element\nAssert.HasCount(3, collection);\nAssert.IsEmpty(collection);\nAssert.IsNotEmpty(collection);\n```\n\nReplace generic `Assert.IsTrue` with specialized assertions -- they give better failure messages:\n\n| Instead of | Use |\n|---|---|\n| `Assert.IsTrue(list.Count > 0)` | `Assert.IsNotEmpty(list)` |\n| `Assert.IsTrue(list.Count == 0)` | `Assert.IsEmpty(list)` |\n| `Assert.IsTrue(list.Count() == 3)` | `Assert.HasCount(3, list)` |\n| `Assert.IsTrue(x != null)` | `Assert.IsNotNull(x)` |\n| `Assert.IsTrue(x == null)` | `Assert.IsNull(x)` |\n| `Assert.AreEqual(a, b)` for same instance | `Assert.AreSame(a, b)` -- reference identity |\n| `Assert.IsTrue(!list.Contains(item))` | `Assert.DoesNotContain(item, list)` |\n| `list.Single(predicate)` + `Assert.IsNotNull` | `Assert.ContainsSingle(list)` |\n| `Assert.IsTrue(list.Contains(item))` | `Assert.Contains(item, list)` |\n\n#### String assertions\n\n```csharp\nAssert.Contains(\"expected\", actualString);\nAssert.StartsWith(\"prefix\", actualString);\nAssert.EndsWith(\"suffix\", actualString);\nAssert.MatchesRegex(@\"\\d{3}-\\d{4}\", phoneNumber);\n```\n\n#### Type assertions\n\n```csharp\n// MSTest 3.x -- out parameter\nAssert.IsInstanceOfType<MyHandler>(result, out var typed);\ntyped.Handle();\n\n// MSTest 4.x -- returns directly\nvar typed = Assert.IsInstanceOfType<MyHandler>(result);\n```\n\n#### Comparison assertions\n\n```csharp\nAssert.IsGreaterThan(lowerBound, actual);\nAssert.IsLessThan(upperBound, actual);\nAssert.IsInRange(actual, low, high);\n```\n\n### Step 4: Use data-driven tests for multiple inputs\n\n#### DataRow for inline values\n\n```csharp\n[TestMethod]\n[DataRow(1, 2, 3)]\n[DataRow(0, 0, 0, DisplayName = \"Zeros\")]\n[DataRow(-1, 1, 0)]\npublic void Add_ReturnsExpectedSum(int a, int b, int expected)\n{\n    Assert.AreEqual(expected, Calculator.Add(a, b));\n}\n```\n\n#### DynamicData with ValueTuples (preferred for complex data)\n\nPrefer `ValueTuple` return types over `IEnumerable<object[]>` for type safety:\n\n```csharp\n[TestMethod]\n[DynamicData(nameof(DiscountTestData))]\npublic void ApplyDiscount_ReturnsExpectedPrice(decimal price, int percent, decimal expected)\n{\n    var result = PriceCalculator.ApplyDiscount(price, percent);\n    Assert.AreEqual(expected, result);\n}\n\n// ValueTuple -- preferred (MSTest 3.7+)\npublic static IEnumerable<(decimal price, int percent, decimal expected)> DiscountTestData =>\n[\n    (100m, 10, 90m),\n    (200m, 25, 150m),\n    (50m, 0, 50m),\n];\n```\n\nWhen you need metadata per test case, use `TestDataRow<T>`:\n\n```csharp\npublic static IEnumerable<TestDataRow<(decimal price, int percent, decimal expected)>> DiscountTestDataWithMetadata =>\n[\n    new((100m, 10, 90m)) { DisplayName = \"10% discount\" },\n    new((200m, 25, 150m)) { DisplayName = \"25% discount\" },\n    new((50m, 0, 50m)) { DisplayName = \"No discount\" },\n];\n```\n\n### Step 5: Handle test lifecycle correctly\n\n- **Always initialize in the constructor** -- this enables `readonly` fields and works correctly with nullability analyzers (fields are guaranteed non-null after construction)\n- Use `[TestInitialize]` **only** for async initialization, combined with the constructor for sync parts\n- Use `[TestCleanup]` for cleanup that must run even on failure\n- Inject `TestContext` via constructor (MSTest 3.6+)\n\n```csharp\n[TestClass]\npublic sealed class RepositoryTests\n{\n    private readonly TestContext _testContext;\n    private readonly FakeDatabase _db;  // readonly -- guaranteed by constructor\n\n    public RepositoryTests(TestContext testContext)\n    {\n        _testContext = testContext;\n        _db = new FakeDatabase();  // sync init in ctor\n    }\n\n    [TestInitialize]\n    public async Task InitAsync()\n    {\n        // Use TestInitialize ONLY for async setup\n        await _db.SeedAsync();\n    }\n\n    [TestCleanup]\n    public void Cleanup() => _db.Reset();\n}\n```\n\n#### Execution order\n\n1. `[AssemblyInitialize]` -- once per assembly\n2. `[ClassInitialize]` -- once per class\n3. Per test:\n   - With `TestContext` property injection: Constructor -> set `TestContext` property -> `[TestInitialize]`\n   - With constructor injection of `TestContext`: Constructor (receives `TestContext`) -> `[TestInitialize]`\n4. Test method\n5. `[TestCleanup]` -> `DisposeAsync` -> `Dispose` -- per test\n6. `[ClassCleanup]` -- once per class\n7. `[AssemblyCleanup]` -- once per assembly\n\n### Step 6: Apply cancellation and timeout patterns\n\nAlways use `TestContext.CancellationToken` with `[Timeout]`:\n\n```csharp\n[TestMethod]\n[Timeout(5000)]\npublic async Task FetchData_ReturnsWithinTimeout()\n{\n    var result = await _client.GetDataAsync(_testContext.CancellationToken);\n    Assert.IsNotNull(result);\n}\n```\n\n### Step 7: Use advanced features where appropriate\n\n#### Retry flaky tests (MSTest 3.9+)\n\nUse only for genuinely flaky external dependencies (network, file system), not to paper over race conditions or shared state issues.\n\n```csharp\n[TestMethod]\n[Retry(3)]\npublic void ExternalService_EventuallyResponds() { }\n```\n\n#### Conditional execution (MSTest 3.10+)\n\n```csharp\n[TestMethod]\n[OSCondition(OperatingSystems.Windows)]\npublic void WindowsRegistry_ReadsValue() { }\n\n[TestMethod]\n[CICondition(ConditionMode.Exclude)]\npublic void LocalOnly_InteractiveTest() { }\n```\n\n#### Parallelization\n\n```csharp\n[assembly: Parallelize(Workers = 4, Scope = ExecutionScope.MethodLevel)]\n\n[TestClass]\n[DoNotParallelize]  // Opt out specific classes\npublic sealed class DatabaseIntegrationTests { }\n```",
  "applicable_domains": [
    "code",
    "dotnet",
    "engineering"
  ],
  "invocation": [
    "/writing-mstest-tests"
  ],
  "tags": [
    "dotnet-test",
    "dotnet",
    "csharp",
    "microsoft"
  ],
  "authored_by": "anthropics",
  "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-test/skills/writing-mstest-tests/SKILL.md",
  "lifecycle": "stable",
  "category": "dotnet",
  "provenance": {
    "source": "dotnet/skills",
    "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-test/skills/writing-mstest-tests/SKILL.md",
    "author": "Microsoft / .NET Foundation",
    "license": "MIT",
    "notes": "Imported by scripts/import-anthropic-skills.py."
  }
}