-
-
Notifications
You must be signed in to change notification settings - Fork 107
Better exception messages when injected data sources throw #4108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Better exception messages when injected data sources throw #4108
Conversation
Code Review - PR #4108: Better exception messages when injected data sources throwOverviewThis PR improves error handling and messaging when data sources fail during test building, converting silent failures and skipped tests into explicit failed tests with proper exception information. Positive Aspects ✅1. Improved User Experience
2. Structured Error HandlingThe new private readonly struct InstanceCreationResult
{
public bool Success => Exception == null;
public static InstanceCreationResult CreateSuccess(object? instance)
public static InstanceCreationResult CreateFailure(Exception exception)
}This follows good patterns and makes the intent explicit. 3. Better Exception PreservationChanged from catching all exceptions silently ( Suggestions & Concerns 💡1. Struct Nullability Design (Minor)The private InstanceCreationResult(object? instance, Exception? exception)
{
if (exception == null && instance == null)
{
throw new ArgumentException("Either instance or exception must be provided");
}
if (exception != null && instance != null)
{
throw new ArgumentException("Cannot have both instance and exception");
}
Instance = instance;
Exception = exception;
}However, this might be overkill for internal code. If you keep it as-is, consider adding a comment about the invariant. 2. Null-Forgiving Operator Usage (Minor - Line 1454, 1457)yield return await CreateFailedTestForInstanceDataSourceError(metadata, instanceResult.Exception!);
// ...
instanceForMethodDataSources = instanceResult.Instance!;The null-forgiving operators are safe here because they're guarded by the if (instanceResult.Exception is not null)
{
yield return await CreateFailedTestForInstanceDataSourceError(metadata, instanceResult.Exception);
continue;
}3. Code Duplication (Moderate)The same pattern appears twice (lines 459-510 and 518-569): Exception? genericResolutionException = null;
try { ... }
catch (Exception ex) { genericResolutionException = ex; }
if (genericResolutionException != null) {
var failedTest = await CreateFailedTestForDataGenerationError(...);
tests.Add(failedTest);
}
else {
// ... create skipped test
}Consider extracting this into a helper method to reduce duplication: private async Task<AbstractExecutableTest> CreateTestForEmptyDataSource(
TestMetadata metadata,
Type[] resolvedClassGenericArgs,
Exception? genericResolutionException,
// ... other parameters)
{
if (genericResolutionException != null)
{
return await CreateFailedTestForDataGenerationError(metadata, genericResolutionException);
}
var testData = new TestData { /* ... */ };
var testSpecificContext = new TestBuilderContext { /* ... */ };
var test = await BuildTestAsync(metadata, testData, testSpecificContext);
test.Context.SkipReason = "Data source returned no data";
return test;
}4. Performance Consideration (Minor)The private readonly record struct InstanceCreationResult(object? Instance, Exception? Exception)
{
public bool Success => Exception == null;
// ...
}5. Missing Test Coverage (Important)I don't see any new tests added for this change. According to CLAUDE.md, you should:
Consider adding integration tests like: [Test]
public async Task DataSourceThatThrows_CreatesFailedTest()
{
// Test that a failing data source creates a failed test with the exception
}
[Test]
public async Task GenericInferenceFailure_CreatesFailedTestWithException()
{
// Test that generic type inference failures produce helpful error messages
}6. Snapshot Tests (Important)According to CLAUDE.md Rule 2, since this changes test discovery behavior in the reflection mode, you should: dotnet test TUnit.Engine.Tests
# If behavior changed in ways that affect output
dotnet test TUnit.Core.SourceGenerator.TestsHave you verified snapshot tests still pass (or updated them if needed)? Technical Correctness ✅Exception HandlingThe change from Async/AwaitAll async operations are properly awaited, and Code StyleThe code follows TUnit conventions:
Security & Safety ✅
Pre-Commit ChecklistPer CLAUDE.md, please verify:
RecommendationApprove with minor suggestions. This is a valuable improvement that makes debugging much easier. The suggestions above are mostly minor improvements, with the most important being:
The core logic is sound and this significantly improves the developer experience when data sources fail. Rating: 8/10 - Good improvement, needs test coverage to be complete. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR improves error handling for data source exceptions during test discovery by replacing silent test skipping with explicit failed tests that include detailed error messages. When data sources throw exceptions (either during instance creation or generic type resolution), the framework now creates FailedExecutableTest instances that surface the underlying exception, making debugging much easier for test authors.
Key Changes
- Introduced
InstanceCreationResultstruct to capture success/failure states when creating test class instances for method data sources - Enhanced exception handling to capture and report specific exceptions instead of using generic
catchblocks - Modified test building logic to create failed tests (with exception details) instead of silently skipping when data source errors occur
No description provided.