Description
When the xUnit migration code fixer converts Assert.Throws<TException>(), subsequent access to the exception's properties (like .Message or custom properties) no longer compiles because the return type changes from the exception type to ThrowsAssertion<TException>.
Steps to Reproduce
Before (xUnit):
[Fact]
public void ThrowsWithMessage()
{
var mock = new Mock<IFoo>();
mock.Setup(x => x.Submit()).Verifiable();
var mex = Assert.Throws<MockException>(() => mock.Verify());
Assert.True(mex.IsVerificationError); // Access custom property
Assert.Contains("Kaboom!", mex.Message); // Access Message property
}
After code fixer (invalid):
[Test]
public async Task ThrowsWithMessage()
{
var mock = new Mock<IFoo>();
mock.Setup(x => x.Submit()).Verifiable();
var mex = Assert.Throws<MockException>(() => mock.Verify());
Assert.True(mex.IsVerificationError); // CS1061: 'ThrowsAssertion<MockException>' doesn't have IsVerificationError
Assert.Contains("Kaboom!", mex.Message); // CS1061: 'ThrowsAssertion<MockException>' doesn't have Message
}
Compilation Errors:
error CS1061: 'ThrowsAssertion<MockException>' does not contain a definition for 'IsVerificationError'
and no accessible extension method 'IsVerificationError' accepting a first argument of type
'ThrowsAssertion<MockException>' could be found
error CS1061: 'ThrowsAssertion<MockException>' does not contain a definition for 'Message'
and no accessible extension method 'Message' accepting a first argument of type
'ThrowsAssertion<MockException>' could be found
Expected Behavior
The code fixer should properly convert patterns where the exception is captured and its properties are accessed.
In TUnit, this should be:
[Test]
public async Task ThrowsWithMessage()
{
var mock = new Mock<IFoo>();
mock.Setup(x => x.Submit()).Verifiable();
var mex = await Assert.ThrowsAsync<MockException>(() => mock.Verify());
await Assert.That(mex.IsVerificationError).IsTrue();
await Assert.That(mex.Message).Contains("Kaboom!");
}
Or using TUnit's fluent assertion pattern:
await Assert.That(() => mock.Verify())
.ThrowsException<MockException>()
.WithMessage(m => m.Contains("Kaboom!"));
Actual Behavior
The code fixer:
- Keeps the
Assert.Throws<T>() call unchanged (doesn't convert to Assert.ThrowsAsync<T>())
- Creates an ambiguous reference because
Xunit.Assert and TUnit.Assertions.Assert both exist
- Doesn't convert the subsequent assertions that access exception properties
Common Patterns Affected
This issue affects all tests that:
- Capture the thrown exception:
var ex = Assert.Throws<T>(() => ...)
- Then assert on exception properties:
Assert.Equal("message", ex.Message)
Assert.Contains("text", ex.Message)
Assert.True(ex.SomeProperty)
- Any custom exception property access
Environment
- TUnit Version: 1.11.*
- .NET SDK: 10.0.102
- OS: Windows
Suggested Fix
The code fixer should:
- Detect when
Assert.Throws<T>() result is assigned to a variable
- Convert the pattern to TUnit's equivalent that returns the exception:
var ex = await Assert.ThrowsAsync<T>(async () => ...);
- Convert subsequent assertions on exception properties to TUnit assertions:
await Assert.That(ex.Message).Contains("expected text");
await Assert.That(ex.CustomProperty).IsTrue();
Alternatively, if using the fluent pattern:
await Assert.That(action)
.ThrowsException<T>()
.And.HasMessage(m => m.Contains("text"));
Tests Needed
Please add tests to verify:
var ex = Assert.Throws<T>(...) followed by ex.Message access converts correctly
var ex = Assert.Throws<T>(...) followed by custom property access converts correctly
- Multiple assertions on the same exception variable convert correctly
- Nested assertions on exception properties work
- The converted code compiles and executes correctly
Description
When the xUnit migration code fixer converts
Assert.Throws<TException>(), subsequent access to the exception's properties (like.Messageor custom properties) no longer compiles because the return type changes from the exception type toThrowsAssertion<TException>.Steps to Reproduce
Before (xUnit):
After code fixer (invalid):
Compilation Errors:
Expected Behavior
The code fixer should properly convert patterns where the exception is captured and its properties are accessed.
In TUnit, this should be:
Or using TUnit's fluent assertion pattern:
Actual Behavior
The code fixer:
Assert.Throws<T>()call unchanged (doesn't convert toAssert.ThrowsAsync<T>())Xunit.AssertandTUnit.Assertions.Assertboth existCommon Patterns Affected
This issue affects all tests that:
var ex = Assert.Throws<T>(() => ...)Assert.Equal("message", ex.Message)Assert.Contains("text", ex.Message)Assert.True(ex.SomeProperty)Environment
Suggested Fix
The code fixer should:
Assert.Throws<T>()result is assigned to a variableAlternatively, if using the fluent pattern:
Tests Needed
Please add tests to verify:
var ex = Assert.Throws<T>(...)followed byex.Messageaccess converts correctlyvar ex = Assert.Throws<T>(...)followed by custom property access converts correctly