Skip to content

xUnit Migration: Exception property access (.Message, .ParamName) not properly converted #4334

@thomhurst

Description

@thomhurst

Description

When using the TUnit xUnit migration code fixer (TUXU0001), the fixer incorrectly attempts to access exception properties like .Message, .ParamName, .InnerException on ThrowsAssertion<T> objects rather than on the captured exception instance itself, resulting in CS1061 compilation errors.

Steps to Reproduce

  1. Create an xUnit test that captures and inspects an exception:
using Xunit;

[Fact]
public void Test_Exception_Message()
{
    var ex = Assert.Throws<ArgumentException>(() => ThrowException());
    Assert.Equal("param", ex.ParamName);
    Assert.Contains("error occurred", ex.Message);
}
  1. Install TUnit package and run dotnet format analyzers --severity info --diagnostics TUXU0001

Expected Behavior

The code should be converted to properly capture the exception and access its properties:

[Test]
public async Task Test_Exception_Message()
{
    var ex = await Assert.That(() => ThrowException()).Throws<ArgumentException>();
    await Assert.That(ex.ParamName).IsEqualTo("param");
    await Assert.That(ex.Message).Contains("error occurred");
}

Or use a different pattern that allows property access on the captured exception.

Actual Behavior

The code is converted to:

[Test]
public async Task Test_Exception_Message()
{
    var ex = awaitAssert.ThrowsAsync<ArgumentException>(() => ThrowException());
    await Assert.That(ex.ParamName).IsEqualTo("param");  // ex is ThrowsAssertion, not ArgumentException
    await Assert.That(ex.Message).Contains("error occurred");
}

This causes multiple errors:

error CS1061: 'ThrowsAssertion<ArgumentException>' does not contain a definition for 'ParamName'
error CS1061: 'ThrowsAssertion<ArgumentException>' does not contain a definition for 'Message'

Properties Affected

The following exception properties commonly accessed after Assert.Throws<T>() are not properly handled:

  • .Message
  • .ParamName (for ArgumentException)
  • .InnerException
  • .StackTrace
  • Custom exception properties (like .IsVerificationError in Moq's MockException)
  • .Reasons (custom properties)

Impact

This is a compound issue with the awaitAssert problem, but even if awaitAssert were defined correctly, the pattern of capturing an exception and accessing its properties would need different handling.

In the Moq test suite, this resulted in 200+ compilation errors for various exception property accesses.

Environment

  • TUnit Version: 1.9.91
  • .NET SDK: 10.0.101
  • OS: Windows

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions