Running Tests with Invoke-Pester and Reading the Output
- PowerShell
- Pester
- Invoke-Pester
- Output
- Beginners
📦 Run it yourself: download the companion code pack; this part's examples are in the
01-foundations/folder (Pester 5-verified).
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:
# Run from the folder where you saved Get-Square.Tests.ps1
Invoke-Pester -Path ./Get-Square.Tests.ps1More often you point it at a folder, and Pester discovers every *.Tests.ps1 file underneath it, recursively. If you're following along from Part 3, you don't have a Tests folder yet, so make one and copy your test file into it:
# Run from your project folder: makes a Tests folder and copies Part 3's test file into it
New-Item -ItemType Directory -Path ./Tests
Copy-Item -Path ./Get-Square.Tests.ps1 -Destination ./TestsThen point Invoke-Pester at the folder:
# Run from your project folder: runs every *.Tests.ps1 file under Tests 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. Here's the end of a healthy run of the two-test Get-Square.Tests.ps1 from Part 3:
Tests completed in 625ms
Tests Passed: 2, Failed: 0, Skipped: 0, Inconclusive: 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. Passed: 0 with Failed: 0 isn't a success: Pester found the file but ran no tests, for example because the file has no It blocks yet or a tag filter matched nothing. (A wrong path is louder: Pester stops with a No test files were found error.) NotRun matters once you start filtering tests by tags in Part 16. Skipped and Inconclusive count tests marked that way with -Skip or Set-ItResult.
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:
# Fails on purpose: Get-Square adds instead of multiplying, so the 25 test goes red
Describe 'Get-Square' {
BeforeAll {
function Get-Square {
param([int]$Number)
$Number + $Number # BUG: should be *, not +
}
}
It 'returns 25 for an input of 5' {
Get-Square -Number 5 | Should -Be 25
}
It 'returns 0 for an input of 0' {
Get-Square -Number 0 | Should -Be 0
}
}Run it with Invoke-Pester -Path ./Get-Square.Tests.ps1 and Pester 5.8.0 prints this (your own folder shows where this says <your folder>, and the timings will differ):
Starting discovery in 1 files.
Discovery found 2 tests in 144ms.
Running tests.
[-] Get-Square.returns 25 for an input of 5 72ms (59ms|13ms)
Expected 25, but got 10.
at Get-Square -Number 5 | Should -Be 25, <your folder>\Get-Square.Tests.ps1:11
at <ScriptBlock>, <your folder>\Get-Square.Tests.ps1:11
Tests completed in 543ms
Tests Passed: 1, Failed: 1, Skipped: 0, Inconclusive: 0, NotRun: 0Three parts to notice:
[-]and the test name tell you which behavior broke:Get-Square.returns 25 for an input of 5, theDescribename and theItname joined by a dot. 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 ... :11lines point to the exact file and line number, so you go straight to the problem.
The passing test doesn't appear at all. By default, Pester lists only the failures; the summary line still counts the pass.
You diagnosed a bug without reading the function body, purely from the failure.
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:
# Run from the folder where you saved Get-Square.Tests.ps1
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. This run was on Pester 5.8.0; your first line shows whichever 5.x you installed:
Pester v5.8.0
Starting discovery in 1 files.
Discovery found 2 tests in 109ms.
Running tests.
Running tests from '<your folder>\Get-Square.Tests.ps1'
Describing Get-Square
[-] returns 25 for an input of 5 81ms (61ms|20ms)
Expected 25, but got 10.
at Get-Square -Number 5 | Should -Be 25, <your folder>\Get-Square.Tests.ps1:11
at <ScriptBlock>, <your folder>\Get-Square.Tests.ps1:11
[+] returns 0 for an input of 0 4ms (3ms|1ms)
Tests completed in 518ms
Tests Passed: 1, Failed: 1, Skipped: 0, Inconclusive: 0, NotRun: 0The 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 parameter.)
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:
# Run from your project folder
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 doesn't read your console output; it checks the exit code of the whole PowerShell process. Here's the catch: a plain Invoke-Pester sets $LASTEXITCODE inside your session, but the PowerShell process itself still exits with 0 when tests fail. GitHub Actions and Azure Pipelines pass $LASTEXITCODE on for their built-in PowerShell steps, but a pipeline step that runs powershell -File on its own sees 0 and reports success. Don't leave it to the runner. In automation, pass -CI:
# Run from your project folder in a CI pipeline: a failing suite exits PowerShell with a non-zero code
Invoke-Pester -Path ./Tests -CIWhen a test fails, -CI makes Pester end the PowerShell process with a non-zero exit code, which turns the build red. That also closes the session, so don't add -CI in a console window you want to keep. (-CI also writes a testResults.xml file into the current folder.)
Part 24 wires this into real GitHub Actions and Azure Pipelines workflows.
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
Treating
Invoke-Pesteras optional. Pester 5 does run a test file when you execute it directly (./Get-Square.Tests.ps1), but only that one file, with default settings.Invoke-Pester -Pathis how you run a whole folder and pass options such as-Output Detailedor-CI, so make it your habit.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 is what turns a red test into a failed build: $LASTEXITCODE tells you inside your session, and -CI makes the PowerShell process itself exit non-zero in automation.
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.
Posts here are written with AI assistance and tested against real code before publishing.