Skip to content

[file-diet] Refactor Assert.ThrowsException.cs (832 lines) into focused partial-class files #9090

@Evangelink

Description

@Evangelink

Overview

The file src/TestFramework/TestFramework/Assertions/Assert.ThrowsException.cs has grown to 832 lines, making it harder to navigate and maintain. This task involves refactoring it into smaller, more focused partial-class files — following the same pattern already used throughout the Assert type.

Note: The technically-largest file (XxHashShared.cs, 911 lines) was intentionally skipped as it is a verbatim port of .NET runtime code and should not be modified here.

Current State

  • File: src/TestFramework/TestFramework/Assertions/Assert.ThrowsException.cs
  • Size: 832 lines
  • Language: C#
Structural Analysis

The file contains four distinct concerns bundled into a single partial-class file:

Lines Content
28–157 AssertNonStrictThrowsInterpolatedStringHandler<TException> struct — lazy-evaluation ISH for Assert.Throws
159–290 AssertThrowsExactlyInterpolatedStringHandler<TException> struct — lazy-evaluation ISH for Assert.ThrowsExactly
291–388 Public Throws<TException> sync overloads (string, Func<Exception?,string>, interpolated-string-handler forms)
389–482 Public ThrowsExactly<TException> sync overloads (same three forms)
483–610 Public ThrowsAsync<TException> and ThrowsExactlyAsync<TException> overloads
611–789 Private implementation: ThrowsException, ThrowsExceptionAsync, IsThrowsFailing, IsThrowsAsyncFailingAsync, ReportThrowsFailed, GetDisplayTypeName
790–832 Private support types: ThrowsFailureKind enum, ThrowsExceptionState struct, GetTrackedThrowsName helper

The interpolated-string-handler structs alone account for ~270 lines and are the primary driver of file size.

Refactoring Strategy

Proposed File Splits

Split the file into three focused partial-class files inside src/TestFramework/TestFramework/Assertions/:

  1. Assert.ThrowsException.InterpolatedStringHandlers.cs (~270 lines)

    • Contents: AssertNonStrictThrowsInterpolatedStringHandler<TException>, AssertThrowsExactlyInterpolatedStringHandler<TException>
    • Responsibility: Compiler-facing interpolated-string-handler structs that enable deferred message formatting; these are purely infrastructure and benefit from being read in isolation
  2. Assert.ThrowsException.cs (~270 lines, trimmed from current 832)

    • Contents: All public Throws<TException> and ThrowsExactly<TException> sync overloads, all public ThrowsAsync<TException> and ThrowsExactlyAsync<TException> overloads
    • Responsibility: The public assertion API surface — what test authors actually call
  3. Assert.ThrowsException.Core.cs (~220 lines)

    • Contents: ThrowsException / ThrowsExceptionAsync private dispatch helpers, IsThrowsFailing / IsThrowsAsyncFailingAsync, ReportThrowsFailed, GetDisplayTypeName, ThrowsFailureKind enum, ThrowsExceptionState struct, GetTrackedThrowsName
    • Responsibility: Private implementation shared by all public overloads; keeping it separate makes the public API file noise-free

Implementation Guidelines

  1. Preserve Behavior: All existing functionality must work identically after the split
  2. Maintain Public API: Keep all public AssertNonStrictThrowsInterpolatedStringHandler<TException> and AssertThrowsExactlyInterpolatedStringHandler<TException> symbols exactly as-is (they are referenced in [InterpolatedStringHandlerArgument] attributes in callers)
  3. Partial class continuity: Each new file must declare public sealed partial class Assert in namespace Microsoft.VisualStudio.TestTools.UnitTesting
  4. No import changes needed: All files share the same namespace; no using directives reference these types from the outside
  5. Test After Each Split: Run ./build.sh -test after each incremental move to confirm nothing breaks
  6. One File at a Time: Move the ISH structs first, then the core helpers, to keep diffs reviewable

Acceptance Criteria

  • Original file is split into three focused partial-class files
  • Each new file is under 300 lines
  • All tests pass after refactoring (./build.sh -test)
  • No breaking changes to public API (PublicAPI.Unshipped.txt unchanged or updated correctly)
  • The #pragma warning disable RS0026 at the top of the original file is retained in the file(s) that still carry the affected overloads

Priority: Medium
Effort: Small
Expected Impact: Improved code navigability, easier code review, reduced cognitive load when working on a single concern (e.g., only the ISH structs or only the async overloads)

🤖 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 Daily File Diet workflow. · 159.7 AIC · ⌖ 13.1 AIC · [◷]( · )

  • expires on Jun 14, 2026, 7:35 PM UTC

Metadata

Metadata

Labels

type/automationCreated or maintained by an agentic workflow.type/tech-debtCode health, refactoring, simplification.

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions