From 621d34423bd50b15dcef3fabf0217c51ca3e8499 Mon Sep 17 00:00:00 2001 From: Jakub Jares Date: Thu, 11 Jun 2026 17:48:58 +0200 Subject: [PATCH] Apply value-assertion rules consistently to Should-* siblings The Pester 6 assertion rules in docs/assertion-types.md say generic value assertions reject collections on -Expected, and value assertions unwrap pipeline input via Collect-Input -UnrollInput. A handful of Should-* didn't follow either rule, behaving differently from their siblings: - Should-NotBeString: missing Collect-Input. Multi-item pipeline silently used the last item; non-string Actual silently passed the assertion. Now mirrors Should-NotBeLikeString and throws ArgumentException when Actual isn't a string. - Should-BeAfter / Should-BeBefore: missing Collect-Input. Also missing the -Because parameter declaration entirely, so the documented switch was a no-op. Both fixed. - Should-BeSame / Should-NotBeSame: missing Ensure-ExpectedIsNotCollection, so passing @() to -Expected produced a misleading "not the same instance" failure instead of the standard collection-on-Expected error. Tests added to lock in each rule. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/functions/assert/General/Should-BeSame.ps1 | 2 ++ src/functions/assert/General/Should-NotBeSame.ps1 | 2 ++ src/functions/assert/String/Should-NotBeString.ps1 | 7 +++++++ src/functions/assert/Time/Should-BeAfter.ps1 | 7 ++++++- src/functions/assert/Time/Should-BeBefore.ps1 | 7 ++++++- tst/functions/assert/General/Should-BeSame.Tests.ps1 | 5 +++++ .../assert/General/Should-NotBeSame.Tests.ps1 | 8 ++++++++ .../assert/String/Should-NotBeString.Tests.ps1 | 10 ++++++++++ tst/functions/assert/Time/Should-BeAfter.Tests.ps1 | 11 +++++++++++ tst/functions/assert/Time/Should-BeBefore.Tests.ps1 | 11 +++++++++++ 10 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/functions/assert/General/Should-BeSame.ps1 b/src/functions/assert/General/Should-BeSame.ps1 index fbd6d0beb..47082006d 100644 --- a/src/functions/assert/General/Should-BeSame.ps1 +++ b/src/functions/assert/General/Should-BeSame.ps1 @@ -47,6 +47,8 @@ [String]$Because ) + $null = Ensure-ExpectedIsNotCollection $Expected + if ($Expected -is [ValueType] -or $Expected -is [string]) { throw [ArgumentException]"Should-BeSame compares objects by reference. You provided a value type or a string, those are not reference types and you most likely don't need to compare them by reference, see https://github.com/nohwnd/Assert/issues/6.`n`nAre you trying to compare two values to see if they are equal? Use Should-BeEqual instead." } diff --git a/src/functions/assert/General/Should-NotBeSame.ps1 b/src/functions/assert/General/Should-NotBeSame.ps1 index 52be7f9be..c663688d9 100644 --- a/src/functions/assert/General/Should-NotBeSame.ps1 +++ b/src/functions/assert/General/Should-NotBeSame.ps1 @@ -46,6 +46,8 @@ [String]$Because ) + $null = Ensure-ExpectedIsNotCollection $Expected + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput $Actual = $collectedInput.Actual if ([object]::ReferenceEquals($Expected, $Actual)) { diff --git a/src/functions/assert/String/Should-NotBeString.ps1 b/src/functions/assert/String/Should-NotBeString.ps1 index 051e4fc46..b06f84c2d 100644 --- a/src/functions/assert/String/Should-NotBeString.ps1 +++ b/src/functions/assert/String/Should-NotBeString.ps1 @@ -59,6 +59,13 @@ function Should-NotBeString { [switch]$IgnoreWhitespace ) + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + + if ($Actual -isnot [string]) { + throw [ArgumentException]"Actual is expected to be string, to avoid confusing behavior that -ne operator exhibits with collections. To assert on collections use Should-Any, Should-All or some other collection assertion." + } + if (Test-StringEqual -Expected $Expected -Actual $Actual -CaseSensitive:$CaseSensitive -IgnoreWhitespace:$IgnoreWhiteSpace) { if (-not $CustomMessage) { $formattedMessage = Get-StringNotEqualDefaultFailureMessage -Expected $Expected -Actual $Actual diff --git a/src/functions/assert/Time/Should-BeAfter.ps1 b/src/functions/assert/Time/Should-BeAfter.ps1 index 742383b33..9c0b2ab3f 100644 --- a/src/functions/assert/Time/Should-BeAfter.ps1 +++ b/src/functions/assert/Time/Should-BeAfter.ps1 @@ -80,9 +80,14 @@ [switch] $FromNow, [Parameter(Position = 0, ParameterSetName = "Expected")] - [DateTime] $Expected + [DateTime] $Expected, + + [String] $Because ) + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + # Now is just a syntax marker, we don't need to do anything with it. $Now = $Now diff --git a/src/functions/assert/Time/Should-BeBefore.ps1 b/src/functions/assert/Time/Should-BeBefore.ps1 index 2ee9a3d17..d96be8eb2 100644 --- a/src/functions/assert/Time/Should-BeBefore.ps1 +++ b/src/functions/assert/Time/Should-BeBefore.ps1 @@ -80,9 +80,14 @@ [switch] $FromNow, [Parameter(Position = 0, ParameterSetName = "Expected")] - [DateTime] $Expected + [DateTime] $Expected, + + [String] $Because ) + $collectedInput = Collect-Input -ParameterInput $Actual -PipelineInput $local:Input -IsPipelineInput $MyInvocation.ExpectingInput -UnrollInput + $Actual = $collectedInput.Actual + # Now is just a syntax marker, we don't need to do anything with it. $Now = $Now diff --git a/tst/functions/assert/General/Should-BeSame.Tests.ps1 b/tst/functions/assert/General/Should-BeSame.Tests.ps1 index b691a9b6f..0b72bd828 100644 --- a/tst/functions/assert/General/Should-BeSame.Tests.ps1 +++ b/tst/functions/assert/General/Should-BeSame.Tests.ps1 @@ -42,4 +42,9 @@ Describe "Should-BeSame" { $object = New-Object Diagnostics.Process { Should-BeSame $object "abc" } | Verify-AssertionFailed } + + It "Throws when collection is passed to -Expected" { + $err = { "dummy" | Should-BeSame -Expected @() } | Verify-Throw + $err.Exception | Verify-Type ([ArgumentException]) + } } diff --git a/tst/functions/assert/General/Should-NotBeSame.Tests.ps1 b/tst/functions/assert/General/Should-NotBeSame.Tests.ps1 index 360013546..1b52e18c9 100644 --- a/tst/functions/assert/General/Should-NotBeSame.Tests.ps1 +++ b/tst/functions/assert/General/Should-NotBeSame.Tests.ps1 @@ -30,5 +30,13 @@ Describe "Should-NotBeSame" { Should-NotBeSame $obj $obj } | Verify-AssertionFailed } + + It "Throws when collection is passed to -Expected" { + $err = { + $obj = New-Object -TypeName PSObject + $obj | Should-NotBeSame -Expected @() + } | Verify-Throw + $err.Exception | Verify-Type ([ArgumentException]) + } } diff --git a/tst/functions/assert/String/Should-NotBeString.Tests.ps1 b/tst/functions/assert/String/Should-NotBeString.Tests.ps1 index fe0bf97de..1e0d041a6 100644 --- a/tst/functions/assert/String/Should-NotBeString.Tests.ps1 +++ b/tst/functions/assert/String/Should-NotBeString.Tests.ps1 @@ -50,5 +50,15 @@ Describe "Should-NotBeString" { It "Can be called with positional parameters" { { Should-NotBeString "a" "a" } | Verify-AssertionFailed } + + It "Throws when collection of strings is passed in by pipeline, even if the last string is different from the expected string" { + $err = { "abc", "bde" | Should-NotBeString -Expected "abc" } | Verify-Throw + $err.Exception | Verify-Type ([ArgumentException]) + } + + It "Throws when -Actual is not a string" { + $err = { Should-NotBeString -Expected "abc" -Actual 1 } | Verify-Throw + $err.Exception | Verify-Type ([ArgumentException]) + } } diff --git a/tst/functions/assert/Time/Should-BeAfter.Tests.ps1 b/tst/functions/assert/Time/Should-BeAfter.Tests.ps1 index a331ab35a..9266eaa76 100644 --- a/tst/functions/assert/Time/Should-BeAfter.Tests.ps1 +++ b/tst/functions/assert/Time/Should-BeAfter.Tests.ps1 @@ -49,6 +49,17 @@ Describe "Should-BeAfter" { { $Actual | Should-BeAfter 10minutes -Ago -FromNow } | Verify-Throw } + It "Fails for array input even if the last item is after the expected date" { + $past = [DateTime]::Now.AddDays(-1) + $future = [DateTime]::Now.AddDays(1) + { $past, $future | Should-BeAfter -Expected ([DateTime]::Now) } | Verify-AssertionFailed + } + + It "Has Because parameter" { + $err = { [DateTime]::Now.AddDays(-1) | Should-BeAfter -Expected ([DateTime]::Now) -Because 'I said so' } | Verify-AssertionFailed + $err.Exception.Message | Verify-Like '*because I said so*' + } + It "Can check file creation date" { New-Item -ItemType Directory -Path "TestDrive:\MyFolder" -Force | Out-Null $path = "TestDrive:\MyFolder\test.txt" diff --git a/tst/functions/assert/Time/Should-BeBefore.Tests.ps1 b/tst/functions/assert/Time/Should-BeBefore.Tests.ps1 index c38e0ac81..7b714b4d8 100644 --- a/tst/functions/assert/Time/Should-BeBefore.Tests.ps1 +++ b/tst/functions/assert/Time/Should-BeBefore.Tests.ps1 @@ -49,6 +49,17 @@ Describe "Should-BeBefore" { { $Actual | Should-BeBefore 10minutes -Ago -FromNow } | Verify-Throw } + It "Fails for array input even if the last item is before the expected date" { + $past = [DateTime]::Now.AddDays(-1) + $future = [DateTime]::Now.AddDays(1) + { $future, $past | Should-BeBefore -Expected ([DateTime]::Now) } | Verify-AssertionFailed + } + + It "Has Because parameter" { + $err = { [DateTime]::Now.AddDays(1) | Should-BeBefore -Expected ([DateTime]::Now) -Because 'I said so' } | Verify-AssertionFailed + $err.Exception.Message | Verify-Like '*because I said so*' + } + It "Can check file creation date" { New-Item -ItemType Directory -Path "TestDrive:\MyFolder" -Force | Out-Null $path = "TestDrive:\MyFolder\test.txt"