Saying "Not": Negating Assertions Cleanly
- powershell
- pester
- testing
- assertions
- negation
📦 Run it yourself: download the companion code pack; this part's examples are in the
02-assertions/folder (Pester 5-verified).
Should -Not -Be reads like plain English, which is exactly why it bites beginners. Negation is easy to write and easy to get wrong, because a test that says "this is not the bad value" can pass for reasons you didn't intend. In this part you'll learn where -Not goes, why negatives are often weak, and how to turn a wobbly negative claim into a confident positive one.
What you'll learn
- Where
-Notgoes in theShouldsyntax - Why "not null" proves almost nothing on its own
- How to prefer a strong positive assertion over a weak negative
- Negating throws, matches, and collection membership
- The traps: double negatives and
-Not -Throwhiding detail
Where -Not goes
-Not is a switch on Should, and it sits between Should and the operator. It simply inverts whatever the operator would have claimed.
# NegationBasics.Tests.ps1
Describe 'Negation basics' {
It 'places -Not before the operator' {
'hello' | Should -Not -Be 'goodbye'
5 | Should -Not -BeGreaterThan 10
@(1,2) | Should -Not -Contain 3
}
}That's the whole mechanic: Should -Not -Be, Should -Not -Match, Should -Not -Contain, Should -Not -Throw. Pester also accepts -Not after the operator, but Should -Not -Be reads like English, so put it there. Should -BeNot isn't an operator.
Why "not null" is a weak claim
The most common negative assertion is Should -Not -BeNullOrEmpty, and it's far weaker than it looks. Consider a function that's supposed to return a user's full name:
# Get-FullName.Tests.ps1
Describe 'Negation' {
BeforeAll {
function Get-FullName {
param($User)
"$($User.First) $($User.Last)"
}
}
It 'returns something (weak)' {
$result = Get-FullName -User ([PSCustomObject]@{ First = $null; Last = $null })
$result | Should -Not -BeNullOrEmpty
}
}That test passes, even though the function returned " ", a single space. "Not null or empty" is satisfied by literally any non-empty value: a space, the word "error", a stack trace. The assertion told you the function returned a thing, not the right thing. A green test here gives false confidence.
Prefer a positive assertion
The fix is to state what you expect. Put the weak negative and the strong positive side by side:
# WeakVsStrong.Tests.ps1
Describe 'Get-FullName result' {
BeforeAll {
$result = 'Ada Lovelace'
}
It 'weak: passes for " ", "error", anything non-empty' {
$result | Should -Not -BeNullOrEmpty
}
It 'strong: passes only for the value you actually want' {
$result | Should -Be 'Ada Lovelace'
}
}The positive version pins the behavior down. To see it, change 'Ada Lovelace' in the BeforeAll to ' ' (a single space) and run the file again. The weak test stays green, and the strong one goes red with this message from Pester 5.8.0:
[-] Get-FullName result.strong: passes only for the value you actually want 78ms (77ms|1ms)
Expected strings to be the same, but they were different.
Expected length: 12
Actual length: 1
Strings differ at index 0.
Expected: 'Ada Lovelace'
But was: ' '
^Whenever you catch yourself writing a negative, ask: what would a correct result be? and assert that instead.
Sometimes you don't know the exact value (a generated GUID, for example). Even then you can usually find a stronger positive than "not null," such as a format check:
# GuidFormat.Tests.ps1
Describe 'Negation' {
It 'returns a well-formed id (stronger than not-null)' {
$id = [guid]::NewGuid().ToString()
$id | Should -Match '^[0-9a-f]{8}-[0-9a-f]{4}-' # shape, not just presence
}
}When negation is the right tool
Negatives aren't banned. Some claims are inherently negative, and there -Not is exactly right.
# NegationThatFits.Tests.ps1
Describe 'Negation' {
It 'uses negation where it fits' {
# A removed item should NOT be in the list anymore:
$remaining = @('apple', 'cherry')
$remaining | Should -Not -Contain 'banana'
# A sanitized string should NOT contain a script tag:
'Hello world' | Should -Not -Match '<script>'
# A valid call should NOT throw:
{ 1 + 1 } | Should -Not -Throw
}
}The difference: these negate a specific thing ("not banana," "no script tag"), so they fail loudly when that exact thing reappears. A negative claim is strong when it rules out one definite outcome, and weak when it rules out only "nothing at all."
Try it yourself
Take this weak negative test and rewrite it as a stronger positive claim.
# Get-Greeting.Tests.ps1
Describe 'Get-Greeting' {
BeforeAll {
function Get-Greeting {
param([string]$Name)
"Hello, $Name!"
}
}
# Weak: passes even if the greeting is wrong, as long as it's non-empty
It 'returns something' {
Get-Greeting -Name 'Ada' | Should -Not -BeNullOrEmpty
}
# Your turn: replace the above with a positive assertion.
It 'greets the person by name' {
Get-Greeting -Name 'Ada' | Should -Be 'Hello, Ada!'
}
}Break the function (drop the ! or the comma) and notice that only the positive test catches it. The negative one stays green through the bug.
Common mistakes
- Negatives that pass for the wrong reason.
Should -Not -Be 'error'passes for'erro','',$null, and a million other values. You proved one outcome is absent, not that the correct one is present. PreferShould -Be <the right value>. - Double negatives.
Should -Not -BeFalseis a confusing way to writeShould -BeTrue, and-Not -Notstyle stacking is illegal and unreadable. Express the claim positively. -Not -Throwswallowing detail.{ Do-Thing } | Should -Not -Throwpassing only tells you it didn't blow up. IfDo-Thingreturns a value worth checking, assert that value instead. A passingShould -Bealready implies no exception, and it tells you what came back, not just that nothing exploded.
Recap
-Not slots between Should and the operator and simply inverts the claim. Use it for negative facts ("not in the list," "no script tag," "did not throw on valid input"), but resist leaning on Should -Not -BeNullOrEmpty, which passes for any non-empty junk. The strongest test states the right answer with a positive assertion; reach for negation only when the claim is inherently about ruling one specific thing out.
Next up: Part 9, Describe vs Context vs It: Organizing Intent, where we move from single assertions to structuring a growing test file so it reads like a specification.
Posts here are written with AI assistance and tested against real code before publishing.