{
  "schema": "https://ai-atoms.com/schemas/skill-v1.json",
  "type": "skill",
  "id": "skill/dotnet-test-frameworks",
  "version": "1.0.0",
  "name": "dotnet-test-frameworks",
  "description": "Reference data for .NET test framework detection patterns, assertion APIs, skip annotations, setup/teardown methods, and common test smell indicators across MSTest, xUnit, NUnit, and TUnit. Loaded by test analysis skills (test-anti-patterns) as framework-specific lookup tables.",
  "system_prompt_fragment": "# .NET Test Framework Reference\n\nLanguage-specific detection patterns for .NET test frameworks (MSTest, xUnit, NUnit, TUnit).\n\n## Test File Identification\n\n| Framework | Test class markers | Test method markers |\n| --------- | ------------------ | ------------------- |\n| MSTest | `[TestClass]` | `[TestMethod]`, `[DataTestMethod]` |\n| xUnit | *(none — convention-based)* | `[Fact]`, `[Theory]` |\n| NUnit | `[TestFixture]` | `[Test]`, `[TestCase]`, `[TestCaseSource]` |\n| TUnit | *(none — convention-based)* | `[Test]` |\n\n## Assertion APIs by Framework\n\n| Category | MSTest | xUnit | NUnit | TUnit |\n| -------- | ------ | ----- | ----- | ----- |\n| Equality | `Assert.AreEqual` | `Assert.Equal` | `Assert.That(x, Is.EqualTo(y))` | `await Assert.That(x).IsEqualTo(y)` |\n| Boolean | `Assert.IsTrue` / `Assert.IsFalse` | `Assert.True` / `Assert.False` | `Assert.That(x, Is.True)` | `await Assert.That(x).IsTrue()` / `await Assert.That(x).IsFalse()` |\n| Null | `Assert.IsNull` / `Assert.IsNotNull` | `Assert.Null` / `Assert.NotNull` | `Assert.That(x, Is.Null)` | `await Assert.That(x).IsNull()` / `await Assert.That(x).IsNotNull()` |\n| Exception | `Assert.Throws<T>()` / `Assert.ThrowsExactly<T>()` | `Assert.Throws<T>()` | `Assert.That(() => ..., Throws.TypeOf<T>())` | `await Assert.That(() => ...).Throws<T>()` / `await Assert.That(() => ...).ThrowsExactly<T>()` |\n| Collection | `CollectionAssert.Contains` | `Assert.Contains` | `Assert.That(col, Has.Member(x))` | `await Assert.That(col).Contains(x)` |\n| String | `StringAssert.Contains` | `Assert.Contains(str, sub)` | `Assert.That(str, Does.Contain(sub))` | `await Assert.That(str).Contains(sub)` |\n| Type | `Assert.IsInstanceOfType` | `Assert.IsAssignableFrom` | `Assert.That(x, Is.InstanceOf<T>())` | `await Assert.That(x).IsAssignableTo<T>()` (use `await Assert.That(x).IsTypeOf<T>()` for exact-type check) |\n| Inconclusive | `Assert.Inconclusive()` | *skip via `[Fact(Skip)]`* | `Assert.Inconclusive()` | `Skip.Test(\"reason\")` (no true inconclusive state) |\n| Fail | `Assert.Fail()` | `Assert.Fail()` (.NET 10+) | `Assert.Fail()` | `Assert.Fail()` |\n\n**TUnit-specific:** assertions are async and **must be awaited** — a forgotten `await` causes the assertion to never run, and the test passes silently. A built-in analyzer warns when `await` is missing. Multiple assertions can be combined with `.And` / `.Or` chaining or grouped via `Assert.Multiple()`.\n\nThird-party assertion libraries: `Should*` (Shouldly), `.Should()` (FluentAssertions / AwesomeAssertions), `Verify()` (Verify). TUnit also ships an optional `TUnit.Assertions.Should` package providing FluentAssertions-style `value.Should().BeEqualTo(...)` on top of the same infrastructure.\n\n## Sleep/Delay Patterns\n\n| Pattern | Example |\n| ------- | ------- |\n| Thread sleep | `Thread.Sleep(2000)` |\n| Task delay | `await Task.Delay(1000)` |\n| SpinWait | `SpinWait.SpinUntil(() => condition, timeout)` |\n\n## Skip/Ignore Annotations\n\n| Framework | Annotation | With reason |\n| --------- | ---------- | ----------- |\n| MSTest | `[Ignore]` | `[Ignore(\"reason\")]` |\n| xUnit | `[Fact(Skip = \"reason\")]` | *(reason is required)* |\n| NUnit | `[Ignore(\"reason\")]` | *(reason is required)* |\n| TUnit | `[Skip(\"reason\")]` | *(reason is required; also valid at class and assembly scope, e.g. `[assembly: Skip(\"…\")]`. Dynamic in-test skipping via `Skip.Test(\"reason\")`.)* |\n| Conditional | `#if false` / `#if NEVER` | *(no reason possible)* |\n\n## Exception Handling — Idiomatic Alternatives\n\nWhen a test uses `try`/`catch` to verify exceptions, suggest the framework-native alternative:\n\n**MSTest:**\n\n```csharp\n// Instead of try/catch (matches exact type):\nvar ex = Assert.ThrowsExactly<InvalidOperationException>(\n    () => processor.ProcessOrder(emptyOrder));\nAssert.AreEqual(\"Order must contain at least one item\", ex.Message);\n\n// Or (also matches derived types):\nvar ex = Assert.Throws<InvalidOperationException>(\n    () => processor.ProcessOrder(emptyOrder));\nAssert.AreEqual(\"Order must contain at least one item\", ex.Message);\n```\n\n**xUnit:**\n\n```csharp\nvar ex = Assert.Throws<InvalidOperationException>(\n    () => processor.ProcessOrder(emptyOrder));\nAssert.Equal(\"Order must contain at least one item\", ex.Message);\n```\n\n**NUnit:**\n\n```csharp\nvar ex = Assert.Throws<InvalidOperationException>(\n    () => processor.ProcessOrder(emptyOrder));\nAssert.That(ex.Message, Is.EqualTo(\"Order must contain at least one item\"));\n```\n\n**TUnit:**\n\n```csharp\nawait Assert.That(() => processor.ProcessOrder(emptyOrder))\n    .Throws<InvalidOperationException>()\n    .WithMessage(\"Order must contain at least one item\");\n\n// Or, for exact-type matching (no derived types):\nawait Assert.That(() => processor.ProcessOrder(emptyOrder))\n    .ThrowsExactly<InvalidOperationException>();\n```\n\n## Mystery Guest — Common .NET Patterns\n\n| Smell indicator | What to look for |\n| --------------- | ---------------- |\n| File system | `File.ReadAllText`, `File.Exists`, `File.WriteAllBytes`, `Directory.GetFiles`, `Path.Combine` with hard-coded paths |\n| Database | `SqlConnection`, `DbContext` (without in-memory provider), `SqlCommand` |\n| Network | `HttpClient` without `HttpMessageHandler` override, `WebRequest`, `TcpClient` |\n| Environment | `Environment.GetEnvironmentVariable`, `Environment.CurrentDirectory` |\n| Acceptable | `MemoryStream`, `StringReader`, `InMemory` database providers, custom `DelegatingHandler` |\n\n## Integration Test Markers\n\nRecognize these as integration tests (adjust smell severity accordingly):\n\n- Class name contains `Integration`, `E2E`, `EndToEnd`, or `Acceptance`\n- `[TestCategory(\"Integration\")]` (MSTest)\n- `[Trait(\"Category\", \"Integration\")]` (xUnit)\n- `[Category(\"Integration\")]` (NUnit, TUnit)\n- Project name ending in `.IntegrationTests` or `.E2ETests`\n\n## Setup/Teardown Methods\n\n| Framework | Setup | Teardown |\n| --------- | ----- | -------- |\n| MSTest | `[TestInitialize]` or constructor | `[TestCleanup]` or `IDisposable.Dispose` / `IAsyncDisposable.DisposeAsync` |\n| xUnit | constructor | `IDisposable.Dispose` / `IAsyncDisposable.DisposeAsync` |\n| NUnit | `[SetUp]` | `[TearDown]` |\n| TUnit | `[Before(Test)]` or constructor | `[After(Test)]` or `IDisposable.Dispose` / `IAsyncDisposable.DisposeAsync` |\n| MSTest (class) | `[ClassInitialize]` | `[ClassCleanup]` |\n| NUnit (class) | `[OneTimeSetUp]` | `[OneTimeTearDown]` |\n| xUnit (class) | `IClassFixture<T>` | fixture's `Dispose` |\n| TUnit (class) | `[Before(Class)]` | `[After(Class)]` |\n| TUnit (assembly) | `[Before(Assembly)]` | `[After(Assembly)]` |\n| TUnit (session) | `[Before(TestSession)]` | `[After(TestSession)]` |\n\n**TUnit-specific:** `[BeforeEvery(Test)]` / `[AfterEvery(Test)]` (and the `Class` / `Assembly` variants) run for every test/class/assembly across the whole test run — useful for global cross-cutting hooks. Hooks may optionally accept a context object (`TestContext`, `ClassHookContext`, etc.) and/or a `CancellationToken`.",
  "applicable_domains": [
    "code",
    "dotnet",
    "engineering"
  ],
  "invocation": [
    "/dotnet-test-frameworks"
  ],
  "tags": [
    "dotnet-test",
    "dotnet",
    "csharp",
    "microsoft"
  ],
  "authored_by": "anthropics",
  "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-test/skills/dotnet-test-frameworks/SKILL.md",
  "lifecycle": "stable",
  "category": "dotnet",
  "provenance": {
    "source": "dotnet/skills",
    "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-test/skills/dotnet-test-frameworks/SKILL.md",
    "author": "Microsoft / .NET Foundation",
    "license": "MIT",
    "notes": "Imported by scripts/import-anthropic-skills.py."
  }
}