feat: forward the caught exception as InnerException when a delegate expectation fails - #1015
Conversation
Test Results 7 files - 6 7 suites - 6 47s ⏱️ - 3m 24s Results for commit 88fe910. ± Comparison against base commit 24dc08d. This pull request removes 16992 and adds 149 tests. Note that renamed tests count towards both.♻️ This comment has been updated with latest results. |
ea9d228 to
4470295
Compare
…e expectation fails When a delegate expectation failed because the delegate threw, the caught exception was rendered into the failure message as type name plus `Message` and then discarded, so the original stack trace was unrecoverable and the test framework had no inner exception to display. `ConstraintResult` now exposes `FailureCause`, which the delegate constraints (`DoesNotThrow`, `DoesNotThrowExactly`, `Throws`, `ThrowsExactly`, `ExecutesWithin` and the with-value variants) set to the caught exception, and which every wrapping and combining constraint result forwards. It is passed through `Fail.Test(string, Exception?)` down to the test framework adapter, which attaches it as `InnerException` of the framework-specific assertion exception. The failure message text is unchanged.
4470295 to
bb3131e
Compare
…reCause` - Replace the optional `innerException` parameter of `Fail.Test`/`Fail.Unless`/`Fail.When` with dedicated overloads, so that callers compiled against a previous aweXpect.Core version stay binary compatible - Fix the misleading doc comment on the test framework adapter `Fail` method - Add tests in aweXpect.Core.Tests covering the `FailureCause` forwarding (delegate constraints, nodes, constraint result wrappers and the new `Fail`/`FailException` overloads) to satisfy the coverage quality gate
|
There was a problem hiding this comment.
🟡 Changes recommended
The pipeline build scope flag is set to CoreOnly (which can unintentionally reduce CI/package coverage), and there are framework adapter test names that need correction to avoid misleading diagnostics.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR preserves the original exception (including stack trace) when a delegate-based expectation fails due to a thrown exception, by threading that exception through ConstraintResult.FailureCause into Fail.Test(..., Exception?) and finally into framework-specific assertion exceptions as InnerException (without changing the failure message text).
Changes:
- Add
ConstraintResult.FailureCauseand forward it through combining/wrapping results and delegate constraints. - Extend failing APIs/adapters (
Fail.*,ITestFrameworkAdapter.Fail) to accept an optional inner exception and pass it to the underlying test framework assertion exception. - Add/expand tests across core and framework adapter test suites to assert
InnerExceptionforwarding behavior.
File summaries
| File | Description |
|---|---|
| Tests/Frameworks/aweXpect.Frameworks.Xunit3.Tests/Xunit3TestFrameworkTests.cs | Adds Xunit3 adapter test ensuring failure cause becomes InnerException. |
| Tests/Frameworks/aweXpect.Frameworks.Xunit3.Core.Tests/Xunit3CoreTestFrameworkTests.cs | Adds Xunit3-core adapter test for forwarding InnerException. |
| Tests/Frameworks/aweXpect.Frameworks.Xunit2.Tests/Xunit2TestFrameworkTests.cs | Adds Xunit2 adapter test for InnerException forwarding. |
| Tests/Frameworks/aweXpect.Frameworks.Tunit.Tests/TunitTestFrameworkTests.cs | Adds TUnit adapter test for InnerException forwarding. |
| Tests/Frameworks/aweXpect.Frameworks.NUnit4.Tests/NUnit4TestFrameworkTests.cs | Adds NUnit4 adapter test for InnerException forwarding. |
| Tests/Frameworks/aweXpect.Frameworks.NUnit3.Tests/NUnit3TestFrameworkTests.cs | Adds NUnit3 adapter test for InnerException forwarding. |
| Tests/Frameworks/aweXpect.Frameworks.MsTest4.Tests/MsTest4FrameworkTests.cs | Adds MSTest adapter test for InnerException forwarding. |
| Tests/Frameworks/aweXpect.Frameworks.MsTest3.Tests/MsTest3FrameworkTests.cs | Adds MSTest3 adapter test for InnerException forwarding. |
| Tests/Frameworks/aweXpect.Frameworks.Fallback.Tests/FallbackTests.cs | Validates fallback exception includes InnerException when provided. |
| Tests/aweXpect.Tests/Delegates/ThatDelegate.Throws.Within.Tests.cs | Adds coverage for not forwarding when failure is due to timeout (“too late”). |
| Tests/aweXpect.Tests/Delegates/ThatDelegate.Throws.Tests.cs | Adds coverage for forwarding when the “wrong” exception type is thrown. |
| Tests/aweXpect.Tests/Delegates/ThatDelegate.DoesNotThrowExactly.GenericTests.cs | Adds coverage for forwarding when DoesNotThrowExactly fails due to thrown exception. |
| Tests/aweXpect.Tests/Delegates/ThatDelegate.DoesNotThrow.Tests.cs | Adds coverage for forwarding when DoesNotThrow fails due to thrown exception. |
| Tests/aweXpect.Core.Tests/FailTests.cs | Adds coverage for Fail.Test/Unless/When forwarding inner exceptions. |
| Tests/aweXpect.Core.Tests/Delegates/ThatDelegateTests.cs | New core-level tests validating FailureCause propagates through composed delegate expectations. |
| Tests/aweXpect.Core.Tests/Core/Nodes/WhichNodeTests.cs | Adds FailureCause forwarding tests for WhichNode. |
| Tests/aweXpect.Core.Tests/Core/Nodes/OrNodeTests.cs | Adds FailureCause forwarding tests for OrNode. |
| Tests/aweXpect.Core.Tests/Core/Nodes/AndNodeTests.cs | Adds FailureCause forwarding tests for AndNode. |
| Tests/aweXpect.Core.Tests/Core/Initialization/AweXpectInitializationTests.cs | Updates test adapters to include the new Fail(message, innerException) overload. |
| Tests/aweXpect.Core.Tests/Core/Exceptions/FailExceptionTests.cs | Adds coverage for FailException(message, innerException) behavior. |
| Tests/aweXpect.Core.Tests/Core/Constraints/ConstraintResultTests.FromExceptionTests.cs | Adds coverage that FromException exposes FailureCause. |
| Tests/aweXpect.Core.Tests/Core/Constraints/ConstraintResultExtensionsTests.cs | Adds coverage for FailureCause forwarding through result wrappers. |
| Tests/aweXpect.Core.Api.Tests/Expected/aweXpect.Core_netstandard2.0.txt | Updates API baseline for new overloads/property. |
| Tests/aweXpect.Core.Api.Tests/Expected/aweXpect.Core_net8.0.txt | Updates API baseline for new overloads/property. |
| Tests/aweXpect.Core.Api.Tests/Expected/aweXpect.Core_net10.0.txt | Updates API baseline for new overloads/property. |
| Source/aweXpect/That/Delegates/ThatDelegate.ExecutesWithin.cs | Ensures ExecutesWithin constraints expose FailureCause when failing due to exception. |
| Source/aweXpect.Frameworks/FrameworkGenerator.cs | Generates adapter Fail(message, innerException) overloads and (xUnit v3 core) an exception type with inner-exception ctor. |
| Source/aweXpect.Core/Results/ExpectationResult.cs | Passes FailureCause into Fail.Test when throwing on failed expectations. |
| Source/aweXpect.Core/Results/Expectation.cs | Aggregates and forwards FailureCause through combination results and final Fail.Test. |
| Source/aweXpect.Core/Results/DelegateWithValueResult.cs | Exposes FailureCause for delegate-with-value “does not throw” failures. |
| Source/aweXpect.Core/Fail.cs | Adds Fail.Test/Unless/When overloads that accept an optional inner exception. |
| Source/aweXpect.Core/Delegates/ThatDelegate.WithValue.DoesNotThrowExactly.cs | Adds FailureCause forwarding for value delegate constraint. |
| Source/aweXpect.Core/Delegates/ThatDelegate.WithValue.DoesNotThrow.cs | Adds FailureCause forwarding for value delegate constraint. |
| Source/aweXpect.Core/Delegates/ThatDelegate.WithoutValue.DoesNotThrowExactly.cs | Adds FailureCause forwarding for void delegate constraint. |
| Source/aweXpect.Core/Delegates/ThatDelegate.WithoutValue.DoesNotThrow.cs | Adds FailureCause forwarding for void delegate constraint. |
| Source/aweXpect.Core/Delegates/ThatDelegate.ThrowsExactly.cs | Adds FailureCause forwarding when ThrowsExactly fails due to exception. |
| Source/aweXpect.Core/Delegates/ThatDelegate.Throws.cs | Adds FailureCause forwarding for Throws, with special-casing to avoid forwarding on “too late” timeout failures. |
| Source/aweXpect.Core/Core/Nodes/WhichNode.cs | Forwards FailureCause through which-node combined results. |
| Source/aweXpect.Core/Core/Nodes/OrNode.cs | Forwards FailureCause through or-node combined results. |
| Source/aweXpect.Core/Core/Nodes/MappingNode.cs | Forwards FailureCause through mapping-node combined results. |
| Source/aweXpect.Core/Core/Nodes/AsyncMappingNode.cs | Forwards FailureCause through async-mapping combined results. |
| Source/aweXpect.Core/Core/Nodes/AndNode.cs | Forwards FailureCause through and-node combined results. |
| Source/aweXpect.Core/Core/Initialization/AweXpectInitialization.cs | Extends failure dispatch to call adapter overload when an inner exception exists; updates fallback adapter accordingly. |
| Source/aweXpect.Core/Core/Exceptions/FailException.cs | Adds inner-exception constructor for fallback failure exception. |
| Source/aweXpect.Core/Core/Constraints/ConstraintResultExtensions.cs | Forwards FailureCause through common ConstraintResult wrapper types. |
| Source/aweXpect.Core/Core/Constraints/ConstraintResult.FromException.cs | Exposes FailureCause from exception-wrapped result. |
| Source/aweXpect.Core/Core/Constraints/ConstraintResult.cs | Introduces FailureCause on the base result type. |
| Source/aweXpect.Core/Core/Adapters/ITestFrameworkAdapter.cs | Adds Fail(message, innerException) contract to adapters. |
| Pipeline/Build.cs | Changes pipeline build scope flag (currently set to CoreOnly). |
Review details
Suppressed comments (2)
Tests/Frameworks/aweXpect.Frameworks.Tunit.Tests/TunitTestFrameworkTests.cs:23
- The skip TUnit adapter test method name still mentions "Xunit2" and "SkipException", but the test asserts
SkipTestException. Renaming keeps the method name aligned with the actual framework/exception type.
[Test]
public async Task OnFailWithCause_WhenUsingTUnitAsTestFramework_ShouldForwardTheCauseAsInnerException()
{
Exception cause = new InvalidOperationException("my cause");
Tests/Frameworks/aweXpect.Frameworks.Tunit.Tests/TunitTestFrameworkTests.cs:23
- The inconclusive TUnit adapter test method name still mentions "Xunit2"/"XunitException" even though it asserts
InconclusiveTestException. Renaming makes the intent and reported failures consistent with the TUnit framework.
[Test]
public async Task OnFailWithCause_WhenUsingTUnitAsTestFramework_ShouldForwardTheCauseAsInnerException()
{
Exception cause = new InvalidOperationException("my cause");
- Files reviewed: 49/49 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| /// Afterward, you can update the package reference in `Directory.Packages.props` and reset this flag. | ||
| /// </summary> | ||
| readonly BuildScope BuildScope = BuildScope.Default; | ||
| readonly BuildScope BuildScope = BuildScope.CoreOnly; |
| [Test] | ||
| public async Task OnInconclusive_WhenUsingNUnit3AsTestFramework_ShouldThrowAssertionException() | ||
| { |
| [Test] | ||
| public async Task OnFailWithCause_WhenUsingTUnitAsTestFramework_ShouldForwardTheCauseAsInnerException() | ||
| { | ||
| Exception cause = new InvalidOperationException("my cause"); |
|
This is addressed in release v2.36.0. |



When a delegate expectation failed because the delegate threw, the caught exception was rendered into the failure message as type name plus
Messageand then discarded, so the original stack trace was unrecoverable and the test framework had no inner exception to display.ConstraintResultnow exposesFailureCause, which the delegate constraints (DoesNotThrow,DoesNotThrowExactly,Throws,ThrowsExactly,ExecutesWithinand the with-value variants) set to the caught exception, and which every wrapping and combining constraint result forwards. It is passed throughFail.Test(string, Exception?)down to the test framework adapter, which attaches it asInnerExceptionof the framework-specific assertion exception.The failure message text is unchanged.