Testing PowerShell Code with Pester
PowerShell Intermediate/Advanced
Chapter 10 · Testing PowerShell Code with Pester
Every function written across both courses has been verified by eye — run it, read the output, decide it looks right. Pester is PowerShell's own built-in testing framework, and it formalizes exactly that instinct into something repeatable: write the expected behavior down once, and let a single command confirm it's still true every time the code changes.
The Structure: Describe, Context, It
Describe groups every test for one thing — usually one function. It is a single test case, named in plain English describing what's actually being checked. Context (not shown above) nests further, grouping related scenarios inside a Describe — "when the input is negative," "when the file doesn't exist" — for tests large enough to benefit from that extra layer.
Should: Making Assertions
| Assertion | Checks |
|---|---|
| Should -Be | Equal (case-insensitive for strings) |
| Should -BeExactly | Equal, case-sensitive |
| Should -BeNullOrEmpty | Value is $null or empty |
| Should -Contain | A collection contains a given value |
| Should -Throw | The code block raises a terminating error |
| Should -Exist | A file or path actually exists |
Should -Be vs. Should -BeExactly: A Familiar Theme, Again
-eq was case-insensitive by default, with -ceq as the explicit case-sensitive variant. Chapter 2's -match was case-insensitive by default, with -cmatch as the explicit variant. Should -Be follows the identical pattern — case-insensitive comparison by default, with Should -BeExactly as the deliberate opt-in for anything genuinely case-sensitive. Once you've internalized it once, it applies consistently everywhere in PowerShell.
Testing Error Conditions: Should -Throw
The code under test has to be wrapped in a script block ({ ... }) for Should -Throw — running it directly would throw immediately and fail the test before Should ever gets a chance to check anything.
Mocking: Isolating a Function From Its Real Dependencies
Mock replaces a real cmdlet's behavior for the duration of the test — Get-ConfigValue can be tested without a real config file existing anywhere on disk, and without the test depending on that file's contents staying the same over time. This is what actually makes a test trustworthy: it fails only when the function's own logic is wrong, never because of something unrelated in the surrounding environment.
Setup & Teardown: BeforeAll/BeforeEach, and TestDrive:
TestDrive: is a real PSDrive — Fundamentals 2's own provider model at work — that Pester creates automatically for each test run and tears down completely afterward, so file-based tests never leave real files scattered on disk. BeforeAll runs once before every test in its Describe/Context; BeforeEach runs before every single It; AfterAll/AfterEach mirror both for cleanup.
Running Tests: Invoke-Pester
.Tests.ps1 — a file named GetSquareTests.ps1 (missing that literal dot before Tests) won't be picked up at all when Invoke-Pester is run against a folder with no explicit -Path to that one file. It's a real, load-bearing naming convention, not a style preference.
It "returns 0 for an input of 0" { Get-Square -n 0 | Should -Be 0 } before Get-Square even handles that case correctly. Watching it fail, then pass once the code is fixed, is real proof the test is actually checking something, not just quietly passing regardless of what the code does.
Hands-On Exercises
Explain why "HELLO" | Should -Be "hello" passes while "HELLO" | Should -BeExactly "hello" fails, tying this back to Fundamentals 4's own -eq/-ceq comparison behavior.
Explain what Mock Get-Content { return '...' } actually does during a test, and why this matters for writing a test that doesn't depend on a real file existing on disk.
Explain why a test file named GetSquareTests.ps1 (without the dot before "Tests") wouldn't be picked up by Invoke-Pester run against a folder with no explicit -Path to that specific file.
Chapter 10 Quick Reference
Describe/Context/It— group tests for a thing, group scenarios within that, and a single named test caseShould -Be— case-insensitive equality;Should -BeExactly— case-sensitive, the same pattern as-eq/-ceqand-match/-cmatchShould -Throw— wrap the code in{ ... }; testing that it actually raises a terminating errorMock— replaces a real cmdlet's behavior for a test, isolating the function under test from real files/network/stateTestDrive:— a real, auto-cleaned PSDrive for file-based tests (Fundamentals 2's provider model at work)BeforeAll/BeforeEach/AfterAll/AfterEach— setup and teardown at different scopesInvoke-Pester— runs tests; requires the*.Tests.ps1naming convention for automatic discovery- Next chapter: Profiles, Customization & Productivity