.github/skills/dotnet-test-frameworks/SKILL.md
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. DO NOT USE directly — loaded by test analysis skills (test-anti-patterns, exp-test-smell-detection, exp-assertion-quality, exp-test-maintainability, exp-test-tagging) when they need framework-specific lookup tables.
npx skillsauth add microsoft/vstest dotnet-test-frameworksInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Language-specific detection patterns for .NET test frameworks (MSTest, xUnit, NUnit, TUnit).
| Framework | Test class markers | Test method markers |
| --------- | ------------------ | ------------------- |
| MSTest | [TestClass] | [TestMethod], [DataTestMethod] |
| xUnit | (none — convention-based) | [Fact], [Theory] |
| NUnit | [TestFixture] | [Test], [TestCase], [TestCaseSource] |
| TUnit | [ClassDataSource] | [Test] |
| Category | MSTest | xUnit | NUnit |
| -------- | ------ | ----- | ----- |
| Equality | Assert.AreEqual | Assert.Equal | Assert.That(x, Is.EqualTo(y)) |
| Boolean | Assert.IsTrue / Assert.IsFalse | Assert.True / Assert.False | Assert.That(x, Is.True) |
| Null | Assert.IsNull / Assert.IsNotNull | Assert.Null / Assert.NotNull | Assert.That(x, Is.Null) |
| Exception | Assert.Throws<T>() / Assert.ThrowsExactly<T>() | Assert.Throws<T>() | Assert.That(() => ..., Throws.TypeOf<T>()) |
| Collection | CollectionAssert.Contains | Assert.Contains | Assert.That(col, Has.Member(x)) |
| String | StringAssert.Contains | Assert.Contains(str, sub) | Assert.That(str, Does.Contain(sub)) |
| Type | Assert.IsInstanceOfType | Assert.IsAssignableFrom | Assert.That(x, Is.InstanceOf<T>()) |
| Inconclusive | Assert.Inconclusive() | skip via [Fact(Skip)] | Assert.Inconclusive() |
| Fail | Assert.Fail() | Assert.Fail() (.NET 10+) | Assert.Fail() |
Third-party assertion libraries: Should* (Shouldly), .Should() (FluentAssertions / AwesomeAssertions), Verify() (Verify).
| Pattern | Example |
| ------- | ------- |
| Thread sleep | Thread.Sleep(2000) |
| Task delay | await Task.Delay(1000) |
| SpinWait | SpinWait.SpinUntil(() => condition, timeout) |
| Framework | Annotation | With reason |
| --------- | ---------- | ----------- |
| MSTest | [Ignore] | [Ignore("reason")] |
| xUnit | [Fact(Skip = "reason")] | (reason is required) |
| NUnit | [Ignore("reason")] | (reason is required) |
| TUnit | [Skip("reason")] | (reason is required) |
| Conditional | #if false / #if NEVER | (no reason possible) |
When a test uses try/catch to verify exceptions, suggest the framework-native alternative:
MSTest:
// Instead of try/catch (matches exact type):
var ex = Assert.ThrowsExactly<InvalidOperationException>(
() => processor.ProcessOrder(emptyOrder));
Assert.AreEqual("Order must contain at least one item", ex.Message);
// Or (also matches derived types):
var ex = Assert.Throws<InvalidOperationException>(
() => processor.ProcessOrder(emptyOrder));
Assert.AreEqual("Order must contain at least one item", ex.Message);
xUnit:
var ex = Assert.Throws<InvalidOperationException>(
() => processor.ProcessOrder(emptyOrder));
Assert.Equal("Order must contain at least one item", ex.Message);
NUnit:
var ex = Assert.Throws<InvalidOperationException>(
() => processor.ProcessOrder(emptyOrder));
Assert.That(ex.Message, Is.EqualTo("Order must contain at least one item"));
| Smell indicator | What to look for |
| --------------- | ---------------- |
| File system | File.ReadAllText, File.Exists, File.WriteAllBytes, Directory.GetFiles, Path.Combine with hard-coded paths |
| Database | SqlConnection, DbContext (without in-memory provider), SqlCommand |
| Network | HttpClient without HttpMessageHandler override, WebRequest, TcpClient |
| Environment | Environment.GetEnvironmentVariable, Environment.CurrentDirectory |
| Acceptable | MemoryStream, StringReader, InMemory database providers, custom DelegatingHandler |
Recognize these as integration tests (adjust smell severity accordingly):
Integration, E2E, EndToEnd, or Acceptance[TestCategory("Integration")] (MSTest)[Trait("Category", "Integration")] (xUnit)[Category("Integration")] (NUnit).IntegrationTests or .E2ETests| Framework | Setup | Teardown |
| --------- | ----- | -------- |
| MSTest | [TestInitialize] or constructor | [TestCleanup] or IDisposable.Dispose / IAsyncDisposable.DisposeAsync |
| xUnit | constructor | IDisposable.Dispose / IAsyncDisposable.DisposeAsync |
| NUnit | [SetUp] | [TearDown] |
| MSTest (class) | [ClassInitialize] | [ClassCleanup] |
| NUnit (class) | [OneTimeSetUp] | [OneTimeTearDown] |
| xUnit (class) | IClassFixture<T> | fixture's Dispose |
development
Best practices for writing MSTest 3.x/4.x unit tests. Use when the user needs to write, improve, fix, or review MSTest tests, including modern assertions, data-driven tests, test lifecycle, and common anti-patterns. Also use when fixing test issues like swapped Assert.AreEqual arguments, incorrect assertion usage, or modernizing legacy test code. Covers MSTest.Sdk, sealed classes, Assert.Throws, DynamicData with ValueTuples, TestContext, and conditional execution.
development
Build, test, and validate changes in the vstest repository. Use when building vstest projects, running unit tests, smoke tests, or acceptance tests, or when deploying locally built vstest.console for manual testing.
development
Validate that commands documented in skill files actually work. Use when creating, updating, or reviewing skills to ensure all documented commands exit with code 0.
testing
Parse and analyze Visual Studio TRX test result files. Use when asked about slow tests, test durations, test frequency, flaky tests, failure analysis, or test execution patterns from TRX files.