fix: drain pending WhichNode in chained ForWhich calls#955
Conversation
A second call to `ExpectationBuilder.ForWhich` overwrote the pending `_whichNode` field, silently dropping the previous projection's parent constraints. Drain the pending node into the graph before creating the new one in both the sync and async overloads (`GetRootNode` still performs the final drain when no further `ForWhich` is called).
There was a problem hiding this comment.
Pull request overview
Fixes a graph-building bug in ExpectationBuilder.ForWhich where chaining multiple ForWhich calls could overwrite a pending WhichNode, causing earlier projection parent constraints to be silently dropped.
Changes:
- Drain any pending
_whichNodeat the start of both sync and asyncForWhichoverloads before creating a newWhichNode. - Add regression tests covering chained
ForWhichbehavior (ordering and honoring constraints across levels).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| Tests/aweXpect.Core.Tests/Core/ExpectationBuilderTests.cs | Adds regression tests to validate chained ForWhich projections and expectation text ordering. |
| Source/aweXpect.Core/Core/ExpectationBuilder.cs | Ensures pending WhichNode is drained into the node graph before starting another ForWhich chain (sync + async). |
Test Results 23 files - 27 23 suites - 27 7m 51s ⏱️ +42s Results for commit 3825a78. ± Comparison against base commit 891b1b7. This pull request removes 3189 and adds 3167 tests. Note that renamed tests count towards both.♻️ This comment has been updated with latest results. |
The previous regression tests only exercised the sync `ForWhich` overload. Add an analogous test that uses two chained async `ForWhich` calls so the drain logic in the async overload is also covered.
55c65a6 to
3825a78
Compare
🚀 Benchmark ResultsDetails
|
👽 Mutation ResultsaweXpectDetails
The final mutation score is NaN%Coverage Thresholds: high:80 low:60 break:0aweXpect.CoreDetails
The final mutation score is 88.30%Coverage Thresholds: high:80 low:60 break:0 |
|
…ed `ForWhich` calls (#955) by Valentin Breuß
…ed `ForWhich` calls (#955) by Valentin Breuß
`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).
|
This is addressed in release v2.34.0. |



A second call to
ExpectationBuilder.ForWhichoverwrote the pending_whichNodefield, silently dropping the previous projection's parent constraints. Drain the pending node into the graph before creating the new one in both the sync and async overloads (GetRootNodestill performs the final drain when no furtherForWhichis called).