Skip to content

[test-improver] test: add edge case tests for MSTEST0020/0021 analyzers #9663

Description

@github-actions

Goal and Rationale

Both PreferConstructorOverTestInitializeAnalyzer (MSTEST0020) and PreferDisposeOverTestCleanupAnalyzer (MSTEST0021) contain no [TestClass] guard in their diagnostic logic — they fire whenever [TestInitialize] or [TestCleanup] is present on a qualifying method, regardless of whether the containing class has [TestClass]. Additionally, the MSTEST0020 fixer merges [TestInitialize] body into the first non-static constructor found — which may be a parameterized constructor. These behaviors are undocumented in tests.

Approach

Added 3 edge-case tests:

PreferConstructorOverTestInitializeAnalyzerTests (MSTEST0020) — +2 tests

  1. WhenTestInitializeMethodInNonTestClass_Diagnostic: verifies the analyzer fires on [TestInitialize] in a class without [TestClass], and the fixer generates a default constructor.
  2. WhenTestClassHasOnlyParameterizedCtorAndTestInitialize_CodeFix_MergesIntoParameterizedCtor: verifies the fixer merges [TestInitialize] body into a parameterized constructor when no default constructor exists — the fixer's first-non-static-ctor strategy applies regardless of constructor signature.

PreferDisposeOverTestCleanupAnalyzerTests (MSTEST0021) — +1 test
3. WhenTestCleanupMethodInNonTestClass_Diagnostic: verifies the analyzer fires on [TestCleanup] in a class without [TestClass], and the fixer adds IDisposable to the class base list and generates a Dispose method.

Coverage Impact

File Tests Before Tests After
PreferConstructorOverTestInitializeAnalyzerTests 10 12
PreferDisposeOverTestCleanupAnalyzerTests 11 12

Trade-offs

These tests are purely additive, document existing behavior, and have no production code changes.

Test Status

Build clean (0 warnings, 0 errors). All 25 affected tests pass (25/25 on net8.0):

Test run summary: Passed!
  total: 25
  failed: 0
  succeeded: 25

Reproducibility

./build.sh --restore
.dotnet/dotnet test test/UnitTests/MSTest.Analyzers.UnitTests/MSTest.Analyzers.UnitTests.csproj -f net8.0 --no-build -c Debug --filter "ClassName~PreferConstructorOverTestInitialize|ClassName~PreferDisposeOverTestCleanup"

🤖 Automated content by GitHub Copilot. Posted via a maintainer's GitHub token, so it appears under their account — the account owner did not write or approve this content personally. Generated by the Test Improver workflow. · 181.4 AIC · ⌖ 31.8 AIC · ⊞ 13K · [◷]( · )

Add this agentic workflows to your repo

To install this agentic workflow, run

gh aw add githubnext/agentics/workflows/test-improver.md@main

Note

This was originally intended as a pull request, but GitHub Actions is not permitted to create or approve pull requests in this repository.
The changes have been pushed to branch test-assist/mstest0020-0021-edge-cases-b9ee5a3094d707ef.

Click here to create the pull request

To fix the permissions issue, go to SettingsActionsGeneral and enable Allow GitHub Actions to create and approve pull requests. See also: gh-aw FAQ

Show patch preview (154 of 154 lines)
From 918fa3907816265f65a007848a27f9e194a859ae Mon Sep 17 00:00:00 2001
From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
Date: Mon, 6 Jul 2026 23:28:44 +0000
Subject: [PATCH] test: add edge case tests for MSTEST0020/0021 analyzers

Add 3 edge-case tests:

PreferConstructorOverTestInitializeAnalyzer (MSTEST0020):
- WhenTestInitializeMethodInNonTestClass_Diagnostic: verifies the
  analyzer fires on [TestInitialize] methods in classes that do not
  carry [TestClass] (no guard in the analyzer).
- WhenTestClassHasOnlyParameterizedCtorAndTestInitialize_CodeFix_MergesIntoParameterizedCtor:
  verifies the fixer merges TestInitialize body into the first
  non-static constructor even when it is parameterized.

PreferDisposeOverTestCleanupAnalyzer (MSTEST0021):
- WhenTestCleanupMethodInNonTestClass_Diagnostic: verifies the
  analyzer fires on [TestCleanup] methods in classes that do not
  carry [TestClass] (no guard in the analyzer).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
 ...structorOverTestInitializeAnalyzerTests.cs | 72 +++++++++++++++++++
 ...eferDisposeOverTestCleanupAnalyzerTests.cs | 30 ++++++++
 2 files changed, 102 insertions(+)

diff --git a/test/UnitTests/MSTest.Analyzers.UnitTests/PreferConstructorOverTestInitializeAnalyzerTests.cs b/test/UnitTests/MSTest.Analyzers.UnitTests/PreferConstructorOverTestInitializeAnalyzerTests.cs
index d6a1f2d..00920c6 100644
--- a/test/UnitTests/MSTest.Analyzers.UnitTests/PreferConstructorOverTestInitializeAnalyzerTests.cs
+++ b/test/UnitTests/MSTest.Analyzers.UnitTests/PreferConstructorOverTestInitializeAnalyzerTests.cs
@@ -404,6 +404,78 @@ private void SomePrivateMethod()
         await VerifyCS.VerifyCodeFixAsync(code, fixedCode);
     }
 
+    [TestMethod]
+    public async Task WhenTestInitializeMethodInNonTestClass_Diagnostic()
+    {
+        string code = """
+            using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+            public class MyClass
+        
... (truncated)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions