fix: chain projected values across nested WhichNodes#958
Conversation
`ExpectationBuilder.ForWhich` (since #955) drains a pending `_whichNode` into the graph before creating a new one, so two consecutive `ForWhich` calls produce two sibling `WhichNode`s. At evaluation time both nodes received the original outer subject — including the inner one, whose `TSource` is the *outer* node's `TMember`. That made any chained `ForWhich` whose accessor consumes the previous projection (e.g. `Which.X.Which.Y` or `Which.X.And.WhoseParent`) throw "The member type for the actual value in the which node did not match". `WhichNode<TSource, TMember>.IsMetBy` now, when the outer value is not itself a `TSource`, asks the parent's `ConstraintResult.TryGetValue` for a `TSource`. `WhichConstraintResult.TryGetValue` already exposes the projection produced by the previous `WhichNode`, so the chained accessor consumes that value instead of the unrelated outer subject. The original type-mismatch error is still thrown when neither the outer value nor the parent chain can supply a `TSource`. Adds coverage in `WhichNodeTests` (direct, two- and three-level chains plus a regression test for the fallback path) and in `ExpectationBuilderTests` (`ForWhich` and `ForWhich`-async called twice where the second projection consumes the first's result, plus a three-level integration variant).
There was a problem hiding this comment.
Pull request overview
This PR fixes chained ForWhich evaluation so nested WhichNodes can consume the projection produced by a parent node when the original outer subject is not assignable to the inner source type.
Changes:
- Updates
WhichNode.IsMetByto fall back to a parent result’s projected value before throwing a type mismatch. - Adds direct
WhichNoderegression coverage for two-level, three-level, and fallback projection scenarios. - Adds
ExpectationBuilderintegration tests for sync and async chainedForWhichprojections.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
Source/aweXpect.Core/Core/Nodes/WhichNode.cs |
Adds parent-result projection fallback for nested WhichNode source resolution. |
Tests/aweXpect.Core.Tests/Core/Nodes/WhichNodeTests.cs |
Covers nested WhichNode projection chaining and preserves type-mismatch behavior. |
Tests/aweXpect.Core.Tests/Core/ExpectationBuilderTests.cs |
Adds sync/async integration coverage for repeated ForWhich projection chains. |
Test Results 23 files - 27 23 suites - 27 7m 37s ⏱️ ±0s Results for commit 260c070. ± Comparison against base commit 3b2559e. This pull request removes 3180 and adds 3164 tests. Note that renamed tests count towards both.♻️ This comment has been updated with latest results. |
🚀 Benchmark ResultsDetails
|
👽 Mutation ResultsaweXpectDetails
The final mutation score is NaN%Coverage Thresholds: high:80 low:60 break:0aweXpect.CoreDetails
The final mutation score is 96.36%Coverage Thresholds: high:80 low:60 break:0 |
a0ec6d8 to
6ef5fba
Compare
|
…ed `WhichNode`s (#958) by Valentin Breuß
…ed `WhichNode`s (#958) by Valentin Breuß
|
This is addressed in release v2.34.0. |



ExpectationBuilder.ForWhich(since #955) drains a pending_whichNodeinto the graph before creating a new one, so two consecutiveForWhichcalls produce two siblingWhichNodes. At evaluation time both nodes received the original outer subject - including the inner one, whoseTSourceis the outer node'sTMember. That made any chainedForWhichwhose accessor consumes the previous projection (e.g.Which.X.Which.YorWhich.X.And.WhoseParent) throw "The member type for the actual value in the which node did not match".WhichNode<TSource, TMember>.IsMetBynow, when the outer value is not itself aTSource, asks the parent'sConstraintResult.TryGetValuefor aTSource.WhichConstraintResult.TryGetValuealready exposes the projection produced by the previousWhichNode, so the chained accessor consumes that value instead of the unrelated outer subject. The original type-mismatch error is still thrown when neither the outer value nor the parent chain can supply aTSource.Adds coverage in
WhichNodeTests(direct, two- and three-level chains plus a regression test for the fallback path) and inExpectationBuilderTests(ForWhichandForWhich-async called twice where the second projection consumes the first's result, plus a three-level integration variant).