Source profileQuality 85/100

github/awesome-copilot/skills/csharp-mstest/SKILL.md

csharp-mstest

Get best practices for MSTest 3.x/4.x unit testing, including modern assertion APIs and data-driven tests

Source repository stars
37,126
Declared platforms
0
Static risk flags
1
Last source update
2026-07-28
Source checked
2026-07-28

Decision brief

What it does—and where it fits

Your goal is to help me write effective unit tests with modern MSTest, using current APIs and best practices.

Best for

    Not for

    • Tasks that require unconfirmed production actions or broad system permissions.
    • Environments where the pinned source and install steps cannot be inspected.

    Compatibility matrix

    Platform support, with evidence labels

    PlatformStatusEvidenceWhat to check
    CodexNot declaredNo explicit evidencePortability before use
    Claude CodeNot declaredNo explicit evidencePortability before use
    CursorNot declaredNo explicit evidencePortability before use
    Gemini CLINot declaredNo explicit evidencePortability before use
    Open the compatibility checker

    Installation

    Inspect first. Install second.

    The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

    Source-detected install commandSource
    npx skills add https://github.com/github/awesome-copilot --skill "skills/csharp-mstest"
    Safe inspection promptEditorial

    Inspect the Agent Skill "csharp-mstest" from https://github.com/github/awesome-copilot/blob/9933dcad5be5caeb288cebcd370eeeb2fc2f1685/skills/csharp-mstest/SKILL.md at commit 9933dcad5be5caeb288cebcd370eeeb2fc2f1685. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

    Workflow

    What the source asks the agent to do

    1. 01

      Project Setup

      Use a separate test project with naming convention [ProjectName].Tests

      Use a separate test project with naming convention [ProjectName].TestsReference MSTest 3.x+ NuGet packages (includes analyzers)Consider using MSTest.Sdk for simplified project setup
    2. 02

      Test Class Structure

      Use [TestClass] attribute for test classes

      Use [TestClass] attribute for test classesSeal test classes by default for performance and design clarityUse [TestMethod] for test methods (prefer over [DataTestMethod])
    3. 03

      Test Lifecycle

      1. Assembly Initialization - [AssemblyInitialize] (once per test assembly) 2. Class Initialization - [ClassInitialize] (once per test class) 3. Test Initialization (for every test method): 1. Constructor 2. Set TestContext property 3. [TestInitialize] 4. Test Execution - test me…

      Prefer constructors over [TestInitialize] - enables readonly fields and follows standard C patternsUse [TestCleanup] for cleanup that must run even if test failsCombine constructor with async [TestInitialize] when async setup is needed
    4. 04

      Execution Order

      1. Assembly Initialization - [AssemblyInitialize] (once per test assembly) 2. Class Initialization - [ClassInitialize] (once per test class) 3. Test Initialization (for every test method): 1. Constructor 2. Set TestContext property 3. [TestInitialize] 4. Test Execution - test me…

      Assembly Initialization - [AssemblyInitialize] (once per test assembly)Class Initialization - [ClassInitialize] (once per test class)Test Initialization (for every test method):
    5. 05

      Modern Assertion APIs

      MSTest provides three assertion classes: Assert, StringAssert, and CollectionAssert.

      MSTest provides three assertion classes: Assert, StringAssert, and CollectionAssert.Note: Prefer Assert class equivalents when available (e.g., Assert.Contains("expected", actual) over StringAssert.Contains(actual, "expected")).Note: Prefer Assert class equivalents when available (e.g., Assert.Contains).

    Permission review

    Static risk signals and limitations

    Network access

    medium · line 416

    The documentation includes network, browsing, or remote request actions.

    [GitHubWorkItem("https://github.com/owner/repo/issues/42")]

    Evidence record

    Why each signal appears

    EvidenceSourceComputedTestedEditorial
    SignalValueEvidence typeMeaning
    Quality score85/100ComputedDocumentation, specificity, maintenance, and trust rules
    Repository stars37,126SourceRepository attention, not individual Skill quality
    Compatibility0 platformsSourceDeclared in the catalog source record
    Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

    Pinned source

    Provenance and original SKILL.md

    Repository
    github/awesome-copilot
    Skill path
    skills/csharp-mstest/SKILL.md
    Commit
    9933dcad5be5caeb288cebcd370eeeb2fc2f1685
    License
    MIT
    Collected
    2026-07-28
    Default branch
    main
    View the original SKILL.md

    MSTest Best Practices (MSTest 3.x/4.x)

    Your goal is to help me write effective unit tests with modern MSTest, using current APIs and best practices.

    Project Setup

    • Use a separate test project with naming convention [ProjectName].Tests
    • Reference MSTest 3.x+ NuGet packages (includes analyzers)
    • Consider using MSTest.Sdk for simplified project setup
    • Run tests with dotnet test

    Test Class Structure

    • Use [TestClass] attribute for test classes
    • Seal test classes by default for performance and design clarity
    • Use [TestMethod] for test methods (prefer over [DataTestMethod])
    • Follow Arrange-Act-Assert (AAA) pattern
    • Name tests using pattern MethodName_Scenario_ExpectedBehavior
    [TestClass]
    public sealed class CalculatorTests
    {
        [TestMethod]
        public void Add_TwoPositiveNumbers_ReturnsSum()
        {
            // Arrange
            var calculator = new Calculator();
    
            // Act
            var result = calculator.Add(2, 3);
    
            // Assert
            Assert.AreEqual(5, result);
        }
    }
    

    Test Lifecycle

    • Prefer constructors over [TestInitialize] - enables readonly fields and follows standard C# patterns
    • Use [TestCleanup] for cleanup that must run even if test fails
    • Combine constructor with async [TestInitialize] when async setup is needed
    [TestClass]
    public sealed class ServiceTests
    {
        private readonly MyService _service;  // readonly enabled by constructor
    
        public ServiceTests()
        {
            _service = new MyService();
        }
    
        [TestInitialize]
        public async Task InitAsync()
        {
            // Use for async initialization only
            await _service.WarmupAsync();
        }
    
        [TestCleanup]
        public void Cleanup() => _service.Reset();
    }
    

    Execution Order

    1. Assembly Initialization - [AssemblyInitialize] (once per test assembly)
    2. Class Initialization - [ClassInitialize] (once per test class)
    3. Test Initialization (for every test method):
      1. Constructor
      2. Set TestContext property
      3. [TestInitialize]
    4. Test Execution - test method runs
    5. Test Cleanup (for every test method):
      1. [TestCleanup]
      2. DisposeAsync (if implemented)
      3. Dispose (if implemented)
    6. Class Cleanup - [ClassCleanup] (once per test class)
    7. Assembly Cleanup - [AssemblyCleanup] (once per test assembly)

    Modern Assertion APIs

    MSTest provides three assertion classes: Assert, StringAssert, and CollectionAssert.

    Assert Class - Core Assertions

    // Equality
    Assert.AreEqual(expected, actual);
    Assert.AreNotEqual(notExpected, actual);
    Assert.AreSame(expectedObject, actualObject);      // Reference equality
    Assert.AreNotSame(notExpectedObject, actualObject);
    
    // Null checks
    Assert.IsNull(value);
    Assert.IsNotNull(value);
    
    // Boolean
    Assert.IsTrue(condition);
    Assert.IsFalse(condition);
    
    // Fail/Inconclusive
    Assert.Fail("Test failed due to...");
    Assert.Inconclusive("Test cannot be completed because...");
    

    Exception Testing (Prefer over [ExpectedException])

    // Assert.Throws - matches TException or derived types
    var ex = Assert.Throws<ArgumentException>(() => Method(null));
    Assert.AreEqual("Value cannot be null.", ex.Message);
    
    // Assert.ThrowsExactly - matches exact type only
    var ex = Assert.ThrowsExactly<InvalidOperationException>(() => Method());
    
    // Async versions
    var ex = await Assert.ThrowsAsync<HttpRequestException>(async () => await client.GetAsync(url));
    var ex = await Assert.ThrowsExactlyAsync<InvalidOperationException>(async () => await Method());
    

    Collection Assertions (Assert class)

    Assert.Contains(expectedItem, collection);
    Assert.DoesNotContain(unexpectedItem, collection);
    Assert.ContainsSingle(collection);  // exactly one element
    Assert.HasCount(5, collection);
    Assert.IsEmpty(collection);
    Assert.IsNotEmpty(collection);
    

    String Assertions (Assert class)

    Assert.Contains("expected", actualString);
    Assert.StartsWith("prefix", actualString);
    Assert.EndsWith("suffix", actualString);
    Assert.DoesNotStartWith("prefix", actualString);
    Assert.DoesNotEndWith("suffix", actualString);
    Assert.MatchesRegex(@"\d{3}-\d{4}", phoneNumber);
    Assert.DoesNotMatchRegex(@"\d+", textOnly);
    

    Comparison Assertions

    Assert.IsGreaterThan(lowerBound, actual);
    Assert.IsGreaterThanOrEqualTo(lowerBound, actual);
    Assert.IsLessThan(upperBound, actual);
    Assert.IsLessThanOrEqualTo(upperBound, actual);
    Assert.IsInRange(actual, low, high);
    Assert.IsPositive(number);
    Assert.IsNegative(number);
    

    Type Assertions

    // MSTest 3.x - uses out parameter
    Assert.IsInstanceOfType<MyClass>(obj, out var typed);
    typed.DoSomething();
    
    // MSTest 4.x - returns typed result directly
    var typed = Assert.IsInstanceOfType<MyClass>(obj);
    typed.DoSomething();
    
    Assert.IsNotInstanceOfType<WrongType>(obj);
    

    Assert.That (MSTest 4.0+)

    Assert.That(result.Count > 0);  // Auto-captures expression in failure message
    

    StringAssert Class

    Note: Prefer Assert class equivalents when available (e.g., Assert.Contains("expected", actual) over StringAssert.Contains(actual, "expected")).

    StringAssert.Contains(actualString, "expected");
    StringAssert.StartsWith(actualString, "prefix");
    StringAssert.EndsWith(actualString, "suffix");
    StringAssert.Matches(actualString, new Regex(@"\d{3}-\d{4}"));
    StringAssert.DoesNotMatch(actualString, new Regex(@"\d+"));
    

    CollectionAssert Class

    Note: Prefer Assert class equivalents when available (e.g., Assert.Contains).

    // Containment
    CollectionAssert.Contains(collection, expectedItem);
    CollectionAssert.DoesNotContain(collection, unexpectedItem);
    
    // Equality (same elements, same order)
    CollectionAssert.AreEqual(expectedCollection, actualCollection);
    CollectionAssert.AreNotEqual(unexpectedCollection, actualCollection);
    
    // Equivalence (same elements, any order)
    CollectionAssert.AreEquivalent(expectedCollection, actualCollection);
    CollectionAssert.AreNotEquivalent(unexpectedCollection, actualCollection);
    
    // Subset checks
    CollectionAssert.IsSubsetOf(subset, superset);
    CollectionAssert.IsNotSubsetOf(notSubset, collection);
    
    // Element validation
    CollectionAssert.AllItemsAreInstancesOfType(collection, typeof(MyClass));
    CollectionAssert.AllItemsAreNotNull(collection);
    CollectionAssert.AllItemsAreUnique(collection);
    

    Data-Driven Tests

    DataRow

    [TestMethod]
    [DataRow(1, 2, 3)]
    [DataRow(0, 0, 0, DisplayName = "Zeros")]
    [DataRow(-1, 1, 0, IgnoreMessage = "Known issue #123")]  // MSTest 3.8+
    public void Add_ReturnsSum(int a, int b, int expected)
    {
        Assert.AreEqual(expected, Calculator.Add(a, b));
    }
    

    DynamicData

    The data source can return any of the following types:

    • IEnumerable<(T1, T2, ...)> (ValueTuple) - preferred, provides type safety (MSTest 3.7+)
    • IEnumerable<Tuple<T1, T2, ...>> - provides type safety
    • IEnumerable<TestDataRow> - provides type safety plus control over test metadata (display name, categories)
    • IEnumerable<object[]> - least preferred, no type safety

    Note: When creating new test data methods, prefer ValueTuple or TestDataRow over IEnumerable<object[]>. The object[] approach provides no compile-time type checking and can lead to runtime errors from type mismatches.

    [TestMethod]
    [DynamicData(nameof(TestData))]
    public void DynamicTest(int a, int b, int expected)
    {
        Assert.AreEqual(expected, Calculator.Add(a, b));
    }
    
    // ValueTuple - preferred (MSTest 3.7+)
    public static IEnumerable<(int a, int b, int expected)> TestData =>
    [
        (1, 2, 3),
        (0, 0, 0),
    ];
    
    // TestDataRow - when you need custom display names or metadata
    public static IEnumerable<TestDataRow<(int a, int b, int expected)>> TestDataWithMetadata =>
    [
        new((1, 2, 3)) { DisplayName = "Positive numbers" },
        new((0, 0, 0)) { DisplayName = "Zeros" },
        new((-1, 1, 0)) { DisplayName = "Mixed signs", IgnoreMessage = "Known issue #123" },
    ];
    
    // IEnumerable<object[]> - avoid for new code (no type safety)
    public static IEnumerable<object[]> LegacyTestData =>
    [
        [1, 2, 3],
        [0, 0, 0],
    ];
    

    TestContext

    The TestContext class provides test run information, cancellation support, and output methods. See TestContext documentation for complete reference.

    Accessing TestContext

    // Property (MSTest suppresses CS8618 - don't use nullable or = null!)
    public TestContext TestContext { get; set; }
    
    // Constructor injection (MSTest 3.6+) - preferred for immutability
    [TestClass]
    public sealed class MyTests
    {
        private readonly TestContext _testContext;
    
        public MyTests(TestContext testContext)
        {
            _testContext = testContext;
        }
    }
    
    // Static methods receive it as parameter
    [ClassInitialize]
    public static void ClassInit(TestContext context) { }
    
    // Optional for cleanup methods (MSTest 3.6+)
    [ClassCleanup]
    public static void ClassCleanup(TestContext context) { }
    
    [AssemblyCleanup]
    public static void AssemblyCleanup(TestContext context) { }
    

    Cancellation Token

    Always use TestContext.CancellationToken for cooperative cancellation with [Timeout]:

    [TestMethod]
    [Timeout(5000)]
    public async Task LongRunningTest()
    {
        await _httpClient.GetAsync(url, TestContext.CancellationToken);
    }
    

    Test Run Properties

    TestContext.TestName              // Current test method name
    TestContext.TestDisplayName       // Display name (3.7+)
    TestContext.CurrentTestOutcome    // Pass/Fail/InProgress
    TestContext.TestData              // Parameterized test data (3.7+, in TestInitialize/Cleanup)
    TestContext.TestException         // Exception if test failed (3.7+, in TestCleanup)
    TestContext.DeploymentDirectory   // Directory with deployment items
    

    Output and Result Files

    // Write to test output (useful for debugging)
    TestContext.WriteLine("Processing item {0}", itemId);
    
    // Attach files to test results (logs, screenshots)
    TestContext.AddResultFile(screenshotPath);
    
    // Store/retrieve data across test methods
    TestContext.Properties["SharedKey"] = computedValue;
    

    Advanced Features

    Retry for Flaky Tests (MSTest 3.9+)

    [TestMethod]
    [Retry(3)]
    public void FlakyTest() { }
    

    Conditional Execution (MSTest 3.10+)

    Skip or run tests based on OS or CI environment:

    // OS-specific tests
    [TestMethod]
    [OSCondition(OperatingSystems.Windows)]
    public void WindowsOnlyTest() { }
    
    [TestMethod]
    [OSCondition(OperatingSystems.Linux | OperatingSystems.MacOS)]
    public void UnixOnlyTest() { }
    
    [TestMethod]
    [OSCondition(ConditionMode.Exclude, OperatingSystems.Windows)]
    public void SkipOnWindowsTest() { }
    
    // CI environment tests
    [TestMethod]
    [CICondition]  // Runs only in CI (default: ConditionMode.Include)
    public void CIOnlyTest() { }
    
    [TestMethod]
    [CICondition(ConditionMode.Exclude)]  // Skips in CI, runs locally
    public void LocalOnlyTest() { }
    

    Parallelization

    // Assembly level
    [assembly: Parallelize(Workers = 4, Scope = ExecutionScope.MethodLevel)]
    
    // Disable for specific class
    [TestClass]
    [DoNotParallelize]
    public sealed class SequentialTests { }
    

    Work Item Traceability (MSTest 3.8+)

    Link tests to work items for traceability in test reports:

    // Azure DevOps work items
    [TestMethod]
    [WorkItem(12345)]  // Links to work item #12345
    public void Feature_Scenario_ExpectedBehavior() { }
    
    // Multiple work items
    [TestMethod]
    [WorkItem(12345)]
    [WorkItem(67890)]
    public void Feature_CoversMultipleRequirements() { }
    
    // GitHub issues (MSTest 3.8+)
    [TestMethod]
    [GitHubWorkItem("https://github.com/owner/repo/issues/42")]
    public void BugFix_Issue42_IsResolved() { }
    

    Work item associations appear in test results and can be used for:

    • Tracing test coverage to requirements
    • Linking bug fixes to regression tests
    • Generating traceability reports in CI/CD pipelines

    Common Mistakes to Avoid

    // ❌ Wrong argument order
    Assert.AreEqual(actual, expected);
    // ✅ Correct
    Assert.AreEqual(expected, actual);
    
    // ❌ Using ExpectedException (obsolete)
    [ExpectedException(typeof(ArgumentException))]
    // ✅ Use Assert.Throws
    Assert.Throws<ArgumentException>(() => Method());
    
    // ❌ Using LINQ Single() - unclear exception
    var item = items.Single();
    // ✅ Use ContainsSingle - better failure message
    var item = Assert.ContainsSingle(items);
    
    // ❌ Hard cast - unclear exception
    var handler = (MyHandler)result;
    // ✅ Type assertion - shows actual type on failure
    var handler = Assert.IsInstanceOfType<MyHandler>(result);
    
    // ❌ Ignoring cancellation token
    await client.GetAsync(url, CancellationToken.None);
    // ✅ Flow test cancellation
    await client.GetAsync(url, TestContext.CancellationToken);
    
    // ❌ Making TestContext nullable - leads to unnecessary null checks
    public TestContext? TestContext { get; set; }
    // ❌ Using null! - MSTest already suppresses CS8618 for this property
    public TestContext TestContext { get; set; } = null!;
    // ✅ Declare without nullable or initializer - MSTest handles the warning
    public TestContext TestContext { get; set; }
    

    Test Organization

    • Group tests by feature or component
    • Use [TestCategory("Category")] for filtering
    • Use [TestProperty("Name", "Value")] for custom metadata (e.g., [TestProperty("Bug", "12345")])
    • Use [Priority(1)] for critical tests
    • Enable relevant MSTest analyzers (MSTEST0020 for constructor preference)

    Mocking and Isolation

    • Use Moq or NSubstitute for mocking dependencies
    • Use interfaces to facilitate mocking
    • Mock dependencies to isolate units under test

    Alternatives

    Compare before choosing