Skip to content
Updated 6 min read

The Should Command and Core Operators

  • powershell
  • pester
  • testing
  • assertions
  • should

📦 Run it yourself: download the companion code pack; this part's examples are in the 02-assertions/ folder (Pester 5-verified).

Should is where a test earns its keep. Everything before it (Describe, It, loading your function) is scaffolding. Should is the moment you say what you believe is true. Master five operators and you can write most of the assertions you'll need.

What you'll learn

  • The Actual | Should -Operator Expected shape every assertion follows
  • -Be versus -BeExactly and why case sensitivity matters
  • Checking for "nothing" with -BeNullOrEmpty
  • Asserting booleans with -BeTrue and -BeFalse
  • Checking a value's type with -BeOfType
  • How to read a failure message so you can fix it fast

The shape of an assertion

Every Pester assertion reads left to right: you pipe the actual value into Should, name an operator, and supply the expected value.

'hello' | Should -Be 'hello'

The thing on the left is what your code produced. The operator is the comparison. The thing on the right is what you expected. If they disagree, the test fails and Pester prints both. Keep that mental model and every operator below is just a variation on it.

-Be: everyday equality

-Be is the operator you'll reach for most. It compares two values for equality and, importantly, it is case-insensitive for strings.

# CoreOperators.Tests.ps1
Describe 'Core operators' {
    It 'compares values with -Be' {
        (2 + 2)         | Should -Be 4
        'PowerShell'    | Should -Be 'powershell'   # passes: case-insensitive
    }
}

That second line surprises people. -Be treats 'PowerShell' and 'powershell' as equal. When case matters, you need the next operator.

-BeExactly: equality that respects case

-BeExactly is -Be with case sensitivity turned on. Use it when the difference between Error and error is meaningful: status codes, tokens, generated identifiers.

# BeExactly.Tests.ps1
Describe 'Should operators' {
    It 'respects case with -BeExactly' {
        'PowerShell' | Should -BeExactly 'PowerShell'   # passes
        # 'PowerShell' | Should -BeExactly 'powershell' # would FAIL
    }
}

Rule of thumb: default to -Be; switch to -BeExactly only when you can articulate why case matters.

-BeNullOrEmpty: checking for "nothing"

PowerShell has several flavors of "nothing": $null, an empty string '', and an empty array @(). -BeNullOrEmpty covers all of them in one assertion.

# BeNullOrEmpty.Tests.ps1
Describe 'Should operators' {
    It 'detects empty values' {
        $null | Should -BeNullOrEmpty
        ''    | Should -BeNullOrEmpty
        @()   | Should -BeNullOrEmpty
    }
}

This is handy for asserting that an optional result wasn't populated, or that a cleanup step really cleared a variable.

-BeTrue and -BeFalse: asserting booleans

When the value you're checking is a boolean, say so directly:

# BeTrueBeFalse.Tests.ps1
Describe 'Should operators' {
    It 'asserts boolean results' {
        (5 -gt 3)            | Should -BeTrue
        (Test-Path 'C:\nope-xyz') | Should -BeFalse
    }
}

-BeTrue and -BeFalse make the intent obvious at a glance, much clearer than $result | Should -Be $true. Reserve them for actual booleans; see Common mistakes for why.

-BeOfType: checking the type

Sometimes the value matters less than its type. -BeOfType takes a type literal (the [bracketed] form):

# BeOfType.Tests.ps1
Describe 'Should operators' {
    It 'checks the type of a value' {
        'hello'      | Should -BeOfType [string]
        42           | Should -BeOfType [int]
        (Get-Date)   | Should -BeOfType [datetime]
    }
}

You can also pass the type name as a string (Should -BeOfType 'System.String'), but the [string] literal reads cleaner and is the common style.

Reading the failure message

Make a test fail on purpose so you recognize the output later:

# Fails on purpose: 'cat' is not 'dog', so this shows the failure message
# FailingAssertion.Tests.ps1
Describe 'A failing assertion' {
    It 'shows Expected vs Actual' {
        'cat' | Should -Be 'dog'
    }
}

Save it as a .Tests.ps1 file and run it. Pester 5.8.0 reports this failure (the at lines that follow it point to your file and line):

[-] A failing assertion.shows Expected vs Actual 165ms (147ms|18ms)
 Expected strings to be the same, but they were different.
 String lengths are both 3.
 Strings differ at index 0.
 Expected: 'dog'
 But was:  'cat'
            ^

For two strings, Pester shows a small diff: it says the strings differ, compares their lengths, and the ^ marks the first character that differs. For numbers you get one line instead, like Part 4's Expected 25, but got 10. Either way, the Expected and But was (or but got) parts are the heart of every Pester failure. When a test goes red, read those first: one is what you asked for, the other is what the code actually did. Usually that tells you whether the bug is in the code or in the test.

Try it yourself

Write one It block with three assertions about a single string value:

# MyString.Tests.ps1
Describe 'My string' {
    It 'is the expected value, present, and a string' {
        $name = 'Ada'
        $name | Should -Be 'Ada'
        $name | Should -Not -BeNullOrEmpty
        $name | Should -BeOfType [string]
    }
}

Run it, confirm it's green, then change the $name = 'Ada' line to $name = 'ada' and run it again: it stays green, because -Be ignores case. Now change Should -Be 'Ada' to Should -BeExactly 'Ada' and watch it go red.

Common mistakes

  • Using -Be on floating-point numbers. 0.1 + 0.2 | Should -Be 0.3 can fail because of binary rounding. For decimals, compare within a tolerance using -BeGreaterThan/-BeLessThan (Part 6) or round first: [math]::Round(0.1 + 0.2, 2) | Should -Be 0.3.
  • Reaching for -eq out of habit. $x -eq 5 evaluates before it reaches Should, so Pester only ever sees $true or $false, and you lose the helpful "Expected 5, but got 4" message. Always let the operator do the comparing: $x | Should -Be 5.
  • Asserting truthiness of a non-boolean. 'false' | Should -BeTrue passes, because a non-empty string is truthy in PowerShell. If you mean "equals the string false," use -Be 'false'. Save -BeTrue/-BeFalse for real booleans.

Recap

Should follows one shape: Actual | Should -Operator Expected. -Be handles case-insensitive equality, -BeExactly adds case sensitivity, -BeNullOrEmpty catches the empty cases, -BeTrue/-BeFalse assert real booleans, and -BeOfType checks the type. Read the Expected and But was (or but got) lines on any failure and you'll know where to look. Avoid -Be on floats and don't let -eq swallow your comparison.

Next up: Part 6, Comparing Numbers, Strings, and Collections, where equality gives way to "greater than," "matches," and "contains."

Posts here are written with AI assistance and tested against real code before publishing.

Comments