Testing That Things Fail: Should -Throw
- powershell
- pester
- testing
- assertions
- errors
Half of robust code is what happens when things go wrong: bad input, a missing file, a $null where a value belonged. If you only test the happy path, those error handlers go untested and quietly rot. In this post you'll learn to test the failure paths with Should -Throw—and the one piece of syntax that trips up nearly every beginner.
What you'll learn
- Why the act for
-Throwmust be wrapped in a scriptblock{ } - Asserting the error message with
-ExpectedMessage - Matching the
-ErrorId(theFullyQualifiedErrorId) - Using
Should -Not -Throwfor "this must succeed" - The difference between terminating and non-terminating errors
A function that throws
Here's a small function that validates its input and throws on a bad value. We'll test both the throw and its message.
function Get-Reciprocal {
param([Parameter(Mandatory)][double]$Number)
if ($Number -eq 0) {
throw 'Number cannot be zero.'
}
1 / $Number
}The scriptblock is mandatory
This is the single most common -Throw mistake, so it comes first. To test that something throws, you must hand Should a scriptblock containing the call—not the result of the call.
Describe 'Get-Reciprocal' {
It 'throws when the number is zero' {
{ Get-Reciprocal -Number 0 } | Should -Throw
}
}Notice the curly braces around Get-Reciprocal -Number 0. They wrap the call so Pester can run it and catch the error itself. If you write it without braces—Get-Reciprocal -Number 0 | Should -Throw—PowerShell executes the call immediately, the exception escapes before Should ever sees it, and your test errors out instead of asserting cleanly. For -Throw, always pass a { } block.
Asserting the message
Should -Throw passing only proves something failed. Usually you want to confirm it failed for the right reason, by checking the message with -ExpectedMessage. The match is a wildcard/substring match, so you don't need the full text:
It 'explains why zero is rejected' {
{ Get-Reciprocal -Number 0 } |
Should -Throw -ExpectedMessage '*cannot be zero*'
}The * wildcards mean "the message contains this phrase somewhere." That keeps the test focused on the meaningful words rather than the exact punctuation, which is exactly what you want (more on over-asserting in Common mistakes).
Matching the ErrorId
Every PowerShell error carries a FullyQualifiedErrorId. When you want to assert on the kind of error rather than its human-readable text, use -ErrorId. Built-in errors have stable IDs—Mandatory parameters that go missing, for instance, raise ParameterArgumentValidationError-family IDs:
function Get-Item-Strict {
param([Parameter(Mandatory)][string]$Path)
Get-Item -Path $Path -ErrorAction Stop
}
It 'reports a specific error id when the path is missing' {
{ Get-Item-Strict -Path 'C:\definitely-not-here-xyz' } |
Should -Throw -ErrorId 'PathNotFound,Microsoft.PowerShell.Commands.GetItemCommand'
}You can also throw with your own ID using a structured throw [System.Management.Automation.ErrorRecord]..., but for most beginner code, -ExpectedMessage is the friendlier assertion.
Proving something does not throw
The flip side is just as useful: assert that a valid call completes without error. Should -Not -Throw makes "this must succeed" an explicit, checked claim.
It 'does not throw for a valid number' {
{ Get-Reciprocal -Number 4 } | Should -Not -Throw
}Use this sparingly. If you have a real expected return value, prefer asserting that (Get-Reciprocal -Number 4 | Should -Be 0.25), because a passing -Be already implies no exception was thrown and tells you more. Reach for -Not -Throw when the success is the behavior—a cleanup routine, a validation pass—and there's no meaningful value to check.
Terminating vs non-terminating errors
-Throw only catches terminating errors—the kind raised by throw, or by a cmdlet running with -ErrorAction Stop. A non-terminating error (the default for many cmdlets) writes to the error stream and lets the pipeline keep going, so it will not satisfy Should -Throw:
It 'a non-terminating error does NOT throw' {
# Get-Item normally writes a non-terminating error here, so this would FAIL:
# { Get-Item 'C:\nope-xyz' } | Should -Throw
# Force it to terminate so the test can catch it:
{ Get-Item 'C:\nope-xyz' -ErrorAction Stop } | Should -Throw
}If a -Throw test surprises you by failing even though "it clearly errors," check whether the underlying command throws or merely writes an error. Adding -ErrorAction Stop (or $ErrorActionPreference = 'Stop') promotes it to a terminating error you can assert on.
Try it yourself
Write a function that rejects $null input with a useful message, then test the throw and the message.
function Get-Upper {
param([string]$Text)
if ($null -eq $Text -or $Text -eq '') {
throw 'Text must not be null or empty.'
}
$Text.ToUpper()
}
Describe 'Get-Upper' {
It 'throws on empty input' {
{ Get-Upper -Text '' } | Should -Throw
}
It 'explains why empty input is rejected' {
{ Get-Upper -Text '' } | Should -Throw -ExpectedMessage '*null or empty*'
}
It 'uppercases a valid string' {
Get-Upper -Text 'hi' | Should -Be 'HI'
}
}Run it green, then delete the throw line and watch the first two tests go red—proof that your error handling is actually being exercised.
Common mistakes
- Forgetting the scriptblock braces.
Get-Reciprocal -Number 0 | Should -Throwruns the call beforeShouldcan catch it, so the test errors instead of asserting. The act for-Throwis always{ ... } | Should -Throw. - Over-asserting the exact message.
-ExpectedMessage 'Number cannot be zero.'(no wildcards, exact) breaks the day someone fixes a typo or adds a period. Match the meaningful phrase with wildcards:'*cannot be zero*'. - Ignoring non-terminating errors. A cmdlet that writes a red error but doesn't terminate will never satisfy
-Throw. Add-ErrorAction Stopto promote it, or your "error path" test silently passes while testing nothing.
Recap
Should -Throw tests the unhappy path: wrap the act in { }, optionally pin the reason with -ExpectedMessage (wildcard match) or -ErrorId, and use Should -Not -Throw when success itself is the behavior. Remember that only terminating errors throw—reach for -ErrorAction Stop when a cmdlet only writes a non-terminating error.
Next up: Part 8 — Saying "Not": Negating Assertions Cleanly, where we look at -Not placement and why a negative assertion is often a weaker claim than it appears.