From 10d65aa082361fad5b4f124f4925ebe7433cc9a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Thu, 6 Aug 2026 20:54:15 +0200 Subject: [PATCH 1/2] Hint that -ExpectedMessage uses wildcards when the message looks identical MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Should -Throw -ExpectedMessage matches with -like, so [ ] * ? are wildcards. When the actual message is identical to the expected one except for those characters, the match fails but the failure message printed the same text on both sides ("with message like 'X' ... but the message was 'X'"), which is confusing and bit people migrating from v4 where matching used .Contains. Add a note in that case pointing at wildcard matching and how to escape. Fixes #1793 🤖 --- src/functions/assertions/PesterThrow.ps1 | 11 ++++++++++ .../assertions/PesterThrow.Tests.ps1 | 22 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/functions/assertions/PesterThrow.ps1 b/src/functions/assertions/PesterThrow.ps1 index c6d99805d..1c7d878e8 100644 --- a/src/functions/assertions/PesterThrow.ps1 +++ b/src/functions/assertions/PesterThrow.ps1 @@ -97,11 +97,18 @@ } $filterOnMessage = -not [string]::IsNullOrWhitespace($ExpectedMessage) + $messageFailedOnWildcard = $false if ($filterOnMessage) { $unescapedExpectedMessage = [System.Management.Automation.WildcardPattern]::Unescape($ExpectedMessage) $filters += "message like $(Format-Nicely $unescapedExpectedMessage)" if ($actualExceptionWasThrown -and (-not (Get-DoValuesMatch $actualExceptionMessage $ExpectedMessage))) { $buts += "the message was $(Format-Nicely $actualExceptionMessage)" + # -ExpectedMessage matches with -like. When the actual message is identical to the expected + # one treated literally, the only reason the match failed is unescaped wildcard characters + # ([ ] * ?) in -ExpectedMessage. Flag it so the failure message is not baffling (#1793). + if ($actualExceptionMessage -eq $unescapedExpectedMessage) { + $messageFailedOnWildcard = $true + } } } @@ -122,6 +129,10 @@ $but = Join-And $buts $failureMessage = "Expected an exception$(if($filter) { " with $filter" }) to be thrown,$(Format-Because $Because) but $but. $actualExceptionLine".Trim() + if ($messageFailedOnWildcard) { + $failureMessage = "$failureMessage$([System.Environment]::NewLine) Note: -ExpectedMessage matches using wildcards (-like). The messages are identical except for the wildcard characters [ ] * ? in -ExpectedMessage. Escape them with a backtick (``[) or use [System.Management.Automation.WildcardPattern]::Escape() to match them literally." + } + $ActualValue = $actualExceptionMessage $ExpectedValue = if ($filterOnExceptionType) { "type $(Format-Nicely $ExceptionType)" diff --git a/tst/functions/assertions/PesterThrow.Tests.ps1 b/tst/functions/assertions/PesterThrow.Tests.ps1 index 27159bf0a..7cf1001ee 100644 --- a/tst/functions/assertions/PesterThrow.Tests.ps1 +++ b/tst/functions/assertions/PesterThrow.Tests.ps1 @@ -176,6 +176,28 @@ InPesterModuleScope { $err.Exception.Message -replace "(`r|`n)" -replace '\s+', ' ' -replace '(char:).*$', '$1' | Verify-Equal $assertionMessage } + It 'hints at wildcard matching when the message is identical except for unescaped wildcard characters' { + # #1793: -ExpectedMessage matches with -like, so [ ] * ? are wildcards. When the expected + # and actual messages look identical, the failure is baffling without a hint pointing at it. + $testDrive = (Get-PSDrive TestDrive).Root + $testScriptPath = Join-Path $testDrive test.ps1 + Set-Content -Path $testScriptPath -Value "throw 'value is [1]'" + + $err = { { & $testScriptPath } | Should -Throw -ExpectedMessage 'value is [1]' } | Verify-AssertionFailed + $err.Exception.Message | Verify-Like '*matches using wildcards*' + } + + It 'does not hint at wildcard matching when the messages genuinely differ' { + $testDrive = (Get-PSDrive TestDrive).Root + $testScriptPath = Join-Path $testDrive test.ps1 + Set-Content -Path $testScriptPath -Value "throw 'error1'" + + $err = { { & $testScriptPath } | Should -Throw -ExpectedMessage 'error2' } | Verify-AssertionFailed + if ($err.Exception.Message -like '*matches using wildcards*') { + throw "Did not expect the wildcard hint, but got: $($err.Exception.Message)" + } + } + It 'returns the correct assertion message when exceptions messages differ' { $testDrive = (Get-PSDrive TestDrive).Root $testScriptPath = Join-Path $testDrive test.ps1 From 0031b37f771c4b66b61d472d248120c79ac7c3ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Thu, 6 Aug 2026 21:10:09 +0200 Subject: [PATCH 2/2] Assert the full hint message and place it before the file location MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spell out the whole expected failure message in the test so it is obvious we are matching for the wildcard hint, and move the note before the 'from ' location so it reads in order and the test can assert it cleanly. 🤖 --- src/functions/assertions/PesterThrow.ps1 | 10 ++++++---- tst/functions/assertions/PesterThrow.Tests.ps1 | 5 ++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/functions/assertions/PesterThrow.ps1 b/src/functions/assertions/PesterThrow.ps1 index 1c7d878e8..5f3d3ad5d 100644 --- a/src/functions/assertions/PesterThrow.ps1 +++ b/src/functions/assertions/PesterThrow.ps1 @@ -127,11 +127,13 @@ if ($buts.Count -ne 0) { $filter = Join-And $filters $but = Join-And $buts - $failureMessage = "Expected an exception$(if($filter) { " with $filter" }) to be thrown,$(Format-Because $Because) but $but. $actualExceptionLine".Trim() - - if ($messageFailedOnWildcard) { - $failureMessage = "$failureMessage$([System.Environment]::NewLine) Note: -ExpectedMessage matches using wildcards (-like). The messages are identical except for the wildcard characters [ ] * ? in -ExpectedMessage. Escape them with a backtick (``[) or use [System.Management.Automation.WildcardPattern]::Escape() to match them literally." + $wildcardHint = if ($messageFailedOnWildcard) { + "$([System.Environment]::NewLine) Note: -ExpectedMessage matches using wildcards (-like). The messages are identical except for the wildcard characters [ ] * ? in -ExpectedMessage. Escape them with a backtick (``[) or use [System.Management.Automation.WildcardPattern]::Escape() to match them literally." + } + else { + "" } + $failureMessage = "Expected an exception$(if($filter) { " with $filter" }) to be thrown,$(Format-Because $Because) but $but.$wildcardHint $actualExceptionLine".Trim() $ActualValue = $actualExceptionMessage $ExpectedValue = if ($filterOnExceptionType) { diff --git a/tst/functions/assertions/PesterThrow.Tests.ps1 b/tst/functions/assertions/PesterThrow.Tests.ps1 index 7cf1001ee..18743cacc 100644 --- a/tst/functions/assertions/PesterThrow.Tests.ps1 +++ b/tst/functions/assertions/PesterThrow.Tests.ps1 @@ -183,8 +183,11 @@ InPesterModuleScope { $testScriptPath = Join-Path $testDrive test.ps1 Set-Content -Path $testScriptPath -Value "throw 'value is [1]'" + # spell out the whole expected message (the hint sits before the 'from ' location) + $assertionMessage = "Expected an exception with message like 'value is [1]' to be thrown, but the message was 'value is [1]'. Note: -ExpectedMessage matches using wildcards (-like). The messages are identical except for the wildcard characters [ ] * ? in -ExpectedMessage. Escape them with a backtick (``[) or use [System.Management.Automation.WildcardPattern]::Escape() to match them literally." + $err = { { & $testScriptPath } | Should -Throw -ExpectedMessage 'value is [1]' } | Verify-AssertionFailed - $err.Exception.Message | Verify-Like '*matches using wildcards*' + $err.Exception.Message -replace "(`r|`n)", ' ' -replace '\s+', ' ' -replace ' from .*$', '' | Verify-Equal $assertionMessage } It 'does not hint at wildcard matching when the messages genuinely differ' {