Skip to content

Hint that a piped collection is unwrapped in single-value assertions#2806

Merged
nohwnd merged 6 commits into
mainfrom
fix-should-havetype-pipeline-hint
Jun 29, 2026
Merged

Hint that a piped collection is unwrapped in single-value assertions#2806
nohwnd merged 6 commits into
mainfrom
fix-should-havetype-pipeline-hint

Conversation

@nohwnd

@nohwnd nohwnd commented Jun 29, 2026

Copy link
Copy Markdown
Member

Piping a collection into an assertion that inspects a single value unwraps the collection first — a one-item collection becomes its single element, several items become [Object[]] — so the assertion never sees the collection you piped and fails with a confusing message. This started as a Should-HaveType fix (#2801), but the same surprise applies to every single-value and type assertion, so this PR generalises the hint.

On the failure path only, the assertion recovers the original piped value (via the PipelineSource trick) and appends a hint that explains exactly what the pipeline did.

A type assertion (Should-HaveType, Should-NotHaveType):

Expected value to have type [string[]], but got [Object[]] @('a', 'b').

Hint: You piped a [string[]] into a type assertion, but the pipeline streams a
multi-item collection and re-collects it as [Object[]], so the assertion saw
[Object[]], not the [string[]] you piped. To assert the type of a collection,
pass it as the -Actual argument instead of piping it, e.g. -Actual $value.

A single-value assertion (Should-Be, booleans, strings, comparisons, …), here a one-item collection (@(1) | Should-Be 2):

Expected [int] 2, but got [int] 1.

Hint: You piped a [Object[]] into a single-value assertion, but the pipeline
unwraps a single-item collection to its one element, so the assertion inspected
that single [int] instead of the collection. To assert on a collection use
Should-BeCollection or Should-BeEquivalent; to assert on a single value pass it
as the -Actual argument instead of piping it, e.g. -Actual $value.

What's covered

  • Type: Should-HaveType, Should-NotHaveType.
  • Value: Should-Be, Should-NotBe, Should-BeNull, Should-NotBeNull, Should-BeSame, Should-NotBeSame, Should-BeHashtable, the boolean assertions (Should-BeTrue, Should-BeFalse, Should-BeTruthy, Should-BeFalsy), and the string equality/emptiness assertions (Should-BeString, Should-BeEmptyString, Should-NotBeEmptyString, Should-NotBeWhiteSpaceString).
  • Comparison & time: Should-BeGreaterThan / …OrEqual, Should-BeLessThan / …OrEqual, Should-BeAfter, Should-BeBefore. These threw a native conversion error when a piped multi-item collection was unwrapped to [Object[]]; the comparison is now wrapped so they fail cleanly with the hint, while a genuine non-pipeline error is still rethrown unchanged.

Notes

  • The hint is computed only when an assertion is already failing, so the passing path is untouched.
  • A genuinely piped scalar, an empty collection, or a value passed with -Actual does not trigger it. For type assertions a piped [Object[]] that stays [Object[]] is not flagged — nothing was lost.
  • Deliberately left as-is: the wildcard/regex/-ne string assertions (Should-NotBeString, Should-BeLikeString, Should-NotBeLikeString, Should-MatchString, Should-NotMatchString) already reject a piped collection with their own collection-specific guidance, so they don't get this hint.
  • All wording lives in one place (Get-AssertionGotcha) and is covered shape-by-shape in a new Get-AssertionGotcha.Tests.ps1, which drives the gotcha through a fake unrolling assertion and a fake non-unrolling one, plus an integration check that the real assertions wire the hint up.

Fix #2801

@nohwnd nohwnd added this to the 6.0.0 milestone Jun 29, 2026
@nohwnd nohwnd enabled auto-merge (squash) June 29, 2026 19:32
@nohwnd nohwnd disabled auto-merge June 29, 2026 19:34
@nohwnd nohwnd changed the title Hint that a piped collection is unwrapped in Should-HaveType Hint that a piped collection is unwrapped in single-value assertions Jun 29, 2026
nohwnd and others added 6 commits June 29, 2026 22:56
Piping a collection into Should-HaveType unwraps it - one item becomes a scalar, several become [object[]] - so the assertion no longer sees the original collection type and fails confusingly. Detect that on the failure path and hint to pass the value with -Actual instead.

Also point the collection-on-Expected error at Should-BeCollection so the suggestion matches the new assertions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the heavily-wildcarded Verify-Like match with the exact hint
text, and make the two unwrap cases (multi-item -> [Object[]], one-item
-> scalar) explicit in the -ForEach data and test names.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The PipelineSource trick recovers the original piped collection's real
type and item count, so the hint can be specific instead of generic:

- a single-item collection is unwrapped to its one element, so the
  assertion sees a scalar (e.g. [string]);
- a multi-item collection is streamed and re-collected into [Object[]].

A genuinely piped scalar keeps its type and still gets no hint, which the
trick can tell apart from an unwrapped single-item collection.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ctions

An empty piped collection (Count 0) sends no items, and a multi-item
[Object[]] that is streamed and re-collected back into an [Object[]] never
loses its type, so in both cases there is no unwrapping to explain. Guard
these so the hint only fires when the pipeline genuinely changed the
observable type.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The #2801 hint that explains how piping a collection unwraps it (one item
becomes a scalar, several become [Object[]]) was only shown by
Should-HaveType. The same surprise applies to every assertion that inspects
a single value, so generalise it.

- Add a Scalar variant to Get-AssertionGotcha. It tells the same unwrap
  story as ExactType but for assertions that care about the value rather
  than its type, so it has no "type did not change" guard -- an [Object[]]
  collapsed from a piped collection is still worth pointing out.
- Wire the hint into the single-value and type assertions: Should-Be,
  Should-NotBe, Should-BeNull, Should-NotBeNull, Should-BeSame,
  Should-NotBeSame, Should-NotHaveType, Should-BeHashtable, the boolean
  assertions, and the string assertions that compare a whole value.
- Comparison and time assertions (Should-BeGreaterThan and friends,
  Should-BeAfter, Should-BeBefore) threw a native conversion error when a
  piped multi-item collection was unwrapped to [Object[]]. Wrap the
  comparison so they now fail cleanly with the hint, while still rethrowing
  the original error for a genuine non-pipeline mismatch.
- Add a dedicated Get-AssertionGotcha test file that drives every input
  shape through a fake unrolling assertion and a fake non-unrolling one, so
  the exact wording for all four variants lives in one place, plus an
  integration check that the real assertions wire the hint up. Converge the
  now-redundant exhaustive Should-HaveType wording tests to smoke tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mention the new hint both where the pipeline-unwrap behaviour is explained
(Pipeline vs. -Actual) and in the richer-failure-messages list, with a short
example of the hint a failing type assertion now shows.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@nohwnd nohwnd force-pushed the fix-should-havetype-pipeline-hint branch from 3978ecd to c534662 Compare June 29, 2026 20:59
@nohwnd nohwnd merged commit 0af22cb into main Jun 29, 2026
13 checks passed
@nohwnd nohwnd deleted the fix-should-havetype-pipeline-hint branch June 29, 2026 21:13
nohwnd added a commit to pester/docs that referenced this pull request Jun 29, 2026
* Document the piped-collection assertion hint for v6

Add a "Piping vs. -Actual" subsection to the v5-to-v6 migration guide
explaining that the new Should-* assertions take their actual value from
the pipeline (which unwraps and re-collects collections as [object[]]) or
from -Actual, and showing the failure-path hint that points users back to
-Actual.

Docs for pester/Pester#2806.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Note that the piped-collection hint is best-effort

The pipeline can't reliably distinguish a single-item collection from a
scalar, and a collection's original type isn't visible on the right-hand
side of the pipeline. Pester recovers the piped value through tricks that
work for common cases but not every one, so the hint won't always appear;
recommend -Actual when the exact value or collection type matters.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Should-HaveType: Strange behaviour when comparing types

1 participant