You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This PR significantly improves exception handling in TUnit's hook execution system by ensuring that exceptions from "after" hooks are properly propagated rather than being swallowed. The changes affect three core engine classes and add comprehensive test coverage.
Code Quality & Best Practices ✅
Strengths:
Consistent Exception Handling: All "after" hook methods now follow the same pattern: collect exceptions, log them, and throw wrapped exceptions
Proper Exception Aggregation: Multiple hook failures are correctly aggregated using AggregateException
Separation of Concerns: Before hooks continue to fail fast (appropriate), while after hooks collect all failures before throwing
Good Use of Custom Exceptions: HookFailedException provides a clear distinction between hook failures and test failures
Minor Considerations:
The exception handling logic is duplicated across multiple methods. Consider extracting common patterns into helper methods for better maintainability
Potential Bugs & Issues ⚠️
Logic Improvements:
HookOrchestratingTestExecutorAdapter.cs:146-167: The test state update logic correctly handles the case where a passing test should be marked as failed due to after hook failures
Exception Chaining: The AggregateException handling in lines 170-187 properly combines test and hook failures
Potential Edge Cases:
Consider cancellation scenarios: What happens if hooks are cancelled? The current implementation should handle this correctly since CancellationException will bubble up naturally
Performance Considerations ✅
Minimal Impact: The addition of exception collection (List<Exception>) and conditional throwing adds negligible overhead
Exception Creation: AggregateException creation only occurs on failure paths, so no performance impact on happy paths
Execution Flow: After hooks continue to execute even if earlier ones fail, which is the correct behavior for cleanup scenarios
Security Concerns ✅
Exception Information: The changes properly preserve original exception information without exposing sensitive data
Resource Cleanup: The exception handling doesn't interfere with proper resource disposal in finally blocks
Test Coverage ✅
The new test file AfterTestExceptionTests.cs provides excellent coverage:
Covered Scenarios:
✅ Passing test with failing after hook → Should fail test
✅ Multiple after hooks failing → Should aggregate exceptions
✅ Both test and after hook failing → Should aggregate both failures
✅ Verification that hooks actually execute → Prevents false positives
Test Quality:
Good use of static flags to verify execution order
Proper async/await patterns
Clear, descriptive test names
Covers both sync and async hook scenarios
Behavioral Changes
Before this PR:
After hook exceptions were logged but swallowed
Tests could pass even if cleanup hooks failed
Multiple hook failures were not properly reported
After this PR:
After hook exceptions cause test failure (correct behavior)
Multiple failures are aggregated and reported together
Better visibility into cleanup failures
Recommendations
1. Code Organization (Optional):
// Consider extracting to reduce duplication:privatestaticExceptionCreateHookException(List<Exception>exceptions,stringcontextMessage){returnexceptions.Count==1?newHookFailedException(exceptions[0]):newHookFailedException($"Multiple {contextMessage} hooks failed",newAggregateException(exceptions));}
2. Documentation:
Consider adding XML comments to clarify the new exception behavior, especially for the "after hooks now fail tests" behavioral change.
3. Logging Enhancement (Optional):
The current logging is good, but consider adding the number of failed hooks to make debugging easier:
await_logger.LogErrorAsync($"AfterEveryTest hook failed ({exceptions.Count} total failures): {ex.Message}");
Conclusion
This is an excellent improvement that addresses a significant gap in TUnit's exception handling. The implementation is thorough, well-tested, and follows established patterns. The change from swallowing after-hook exceptions to properly failing tests is the correct behavior for a testing framework.
Recommendation: ✅ Approve
The code quality is high, test coverage is comprehensive, and the behavioral change aligns with user expectations for test framework reliability.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Please check the following before creating a Pull Request