Running Tests with Invoke-Pester and Reading the Output
- PowerShell
- Pester
- Invoke-Pester
- Output
- Beginners
A green checkmark is nice, but the real skill is reading a failure and knowing exactly what broke without opening the source file. In Part 3 you wrote a test and ran it. Here we slow down on the run itself—from pointing Invoke-Pester at a folder to decoding a failure and understanding the exit code that CI depends on.
What you'll learn
- How to run tests on a single file and on a whole folder
- How to read the pass/fail/skipped summary line
- How to decode a failure message (Expected vs But was)
- How
-Output Detailedchanges what you see - What exit codes are and why CI relies on them
Running tests on a file or a folder
Invoke-Pester is the command that finds and runs your tests. The simplest form points it at a single test file:
Invoke-Pester -Path ./Get-Square.Tests.ps1More often you point it at a folder, and Pester discovers every *.Tests.ps1 file underneath it, recursively:
# Runs every *.Tests.ps1 file under the Tests directory and its subfolders
Invoke-Pester -Path ./TestsThat discovery-by-convention is why the .Tests.ps1 suffix matters (more on file layout in Part 12): give Invoke-Pester a folder and it runs the lot.
Reading the summary line
Whatever you run, Pester ends with a one-line summary. A healthy run looks like this:
Tests completed in 142ms
Tests Passed: 2, Failed: 0, Skipped: 0 NotRun: 0Read that line first, every time. Failed: 0 is the number that decides whether you can relax. Passed tells you how much actually ran (a suite that reports Passed: 0 usually means nothing was discovered, often a naming or path problem, not a success). Skipped and NotRun matter once you start filtering tests by tags in Part 16.
Anatomy of a failure
Let us make one fail on purpose, the most useful thing you can do while learning. Save this file as Get-Square.Tests.ps1 with a deliberate bug in the function:
function Get-Square {
param([int]$Number)
$Number + $Number # BUG: should be *, not +
}
Describe 'Get-Square' {
It 'returns 25 for an input of 5' {
Get-Square -Number 5 | Should -Be 25
}
}Run it with Invoke-Pester -Path ./Get-Square.Tests.ps1 and Pester prints a failure block. Annotated, it reads like this:
Describing Get-Square
[-] returns 25 for an input of 5 8ms (5ms|3ms)
Expected 25, but got 10.
at Get-Square -Number 5 | Should -Be 25, /Get-Square.Tests.ps1:8
at <ScriptBlock>, /Get-Square.Tests.ps1:8
Tests completed in 142ms
Tests Passed: 0, Failed: 1, Skipped: 0 NotRun: 0Three parts to notice:
[-]and the test name tell you which behavior broke: "returns 25 for an input of 5." This is why goodItnames pay off.Expected 25, but got 10.is the heart of it. Expected is what yourShould -Beclaimed; but got (sometimes shown as "but was") is what the code actually produced. The gap between them is the bug:5 + 5is10, not25.- The
at ... :8lines point to the exact file and line number, so you go straight to the problem.
You diagnosed a bug without reading the function body, purely from the failure. That is the skill.
Turning up the detail with -Output
By default Pester keeps the console quiet, often showing only failures and the summary. While learning (or hunting a stubborn bug) you usually want to see every test, passing and failing alike. Use -Output Detailed:
Invoke-Pester -Path ./Get-Square.Tests.ps1 -Output DetailedNow you see each Describe, each It, and a green [+] for every pass alongside the red [-] failures:
Describing Get-Square
[+] returns 0 for an input of 0 4ms
[-] returns 25 for an input of 5 8ms
Expected 25, but got 10.The verbosity levels are None, Normal, Detailed, and Diagnostic. Detailed is the sweet spot for day-to-day work; Diagnostic is loud and mostly for debugging Pester itself. (Part 23 sets this through a configuration object instead of a switch.)
Exit codes: why CI cares
Every command leaves behind an exit code, a number that other tools read to decide "did that succeed?" Zero means success; non-zero means failure. After an Invoke-Pester run you can inspect it:
Invoke-Pester -Path ./Tests
$LASTEXITCODE # 0 if all passed, non-zero if any test failedThis is the single most important thing CI systems use. A pipeline does not read your console output, it checks the exit code: if Invoke-Pester exits non-zero, the build goes red and the bad change is blocked. To make sure Pester actually sets that code (it does not always, depending on how it is invoked), pass -CI in automation:
# In CI: ensure a failing suite produces a non-zero exit code
Invoke-Pester -Path ./Tests -CIPart 24 wires this into real GitHub Actions and Azure Pipelines workflows. For now, just remember: green console, exit code 0, happy CI.
Try it yourself
- Save the buggy
Get-Square.Tests.ps1above (with+instead of*). - Run
Invoke-Pester -Path ./Get-Square.Tests.ps1 -Output Detailed. - Read the Expected vs but got line and the line number. Resist the urge to open the function, can you name the bug from the message alone?
- Check the result of
$LASTEXITCODEand confirm it is non-zero. - Fix the function (
*instead of+), rerun, and confirm green with$LASTEXITCODEback to 0.
Common mistakes
Running the
.Tests.ps1file directly. Executing./Get-Square.Tests.ps1(or dot-sourcing it) does not run your tests the way Pester intends, in v5 the file is processed in two phases and needsInvoke-Pesterto drive it. Always run tests throughInvoke-Pester -Path.Ignoring the summary line. It is easy to glance at a wall of output and miss that
Failed: 1. Read the summary first, every time; theFailedcount is the verdict.Not knowing the exit code drives CI. A pipeline does not read your eyes, it reads
$LASTEXITCODE. If you forget-CI(or the equivalent config) in automation, a red suite can still report "build succeeded," which is worse than no tests at all.
Recap
Invoke-Pester -Path runs a single file or discovers every *.Tests.ps1 under a folder. Read the summary line first, the Failed count is your verdict, then decode failures from the Expected-vs-but-got message and the file:line pointer, no need to open the source. -Output Detailed shows passes and failures together while you learn, and the exit code ($LASTEXITCODE, made reliable with -CI) is what turns a red test into a failed build.
Next up: Part 5 — The Should Command and Core Operators, where we leave -Be behind for a moment and meet the handful of assertion operators that cover most of the tests you will ever write.