Setup & Teardown: BeforeAll, AfterAll, BeforeEach, AfterEach
- pester
- powershell
- testing
- beforeall
- discovery
- structure
Pester 5 runs your test file in two distinct phases, and it trips up almost everyone exactly once—usually as a baffling "command not found" on a function you clearly defined two lines up. The fix is simple once you see how it works. This post explains the Discovery vs Run phases and the four blocks—BeforeAll, AfterAll, BeforeEach, AfterEach—that put setup in the right place.
What you'll learn
- The Pester 5 Discovery vs Run phase model and why it exists
- Why setup code belongs in
BeforeAll, never at the top of the file - How
BeforeEach/AfterEachgive each test a clean slate - How variables defined in setup blocks are scoped to the tests inside
The two-phase model (the part that trips everyone up)
When you run Invoke-Pester, Pester does not just execute your file top to bottom. It runs it in two passes:
- Discovery phase — Pester executes the file to find all your
Describe,Context, andItblocks and build a map of the suite. During this pass, the script blocks insideItare not run — Pester is only collecting structure. Crucially,BeforeAll/BeforeEachbodies do not run here either; Pester just registers them. - Run phase — Pester walks the map it built and actually executes the tests: it runs each
BeforeAll, then theItbodies, withBeforeEach/AfterEacharound them, thenAfterAll.
Here's the catch. Any bare code you write at the top of the file — outside any block — runs during Discovery. And the contents of It run during Run. Those are two different moments, and Discovery happens first, for the whole file, before any test executes.
That timing gap is the source of nearly every beginner Pester 5 mystery.
The classic "command not found"
Watch this fail. It looks completely reasonable:
# BROKEN — do not copy this pattern
. $PSScriptRoot/Get-Square.ps1 # bare line at top of file
Describe 'Get-Square' {
It 'squares 3 to 9' {
Get-Square 3 | Should -Be 9
}
}You'd expect the dot-source to load Get-Square so the test can call it. But trace the phases:
- During Discovery, the bare line runs and loads the function — into the Discovery scope.
- During Run, Pester executes the
Itbody in a fresh scope where that function isn't available. You getThe term 'Get-Square' is not recognized.
The setup ran at the wrong time: in Discovery, while the test runs in Run.
The fix: put setup in BeforeAll
BeforeAll is a block whose body runs during the Run phase, once, before the tests in its container. Move the dot-source inside it and the timing lines up:
Describe 'Get-Square' {
BeforeAll {
. $PSScriptRoot/Get-Square.ps1
}
It 'squares 3 to 9' {
Get-Square 3 | Should -Be 9
}
}Now the function loads in the Run phase, in scope for the tests. To make this runnable, create Get-Square.ps1 next to the test file:
function Get-Square {
param([double]$Number)
$Number * $Number
}Then run Invoke-Pester ./Get-Square.Tests.ps1. Green. The rule that follows: anything the tests depend on goes in BeforeAll, not at the top of the file.
Variable scoping from BeforeAll
Variables you assign in BeforeAll are available to every It, BeforeEach, and nested block inside the same container. This is how you share expensive or fixed setup:
Describe 'Order totals' {
BeforeAll {
# Runs once, before all tests in this Describe
$script:catalog = @{ apple = 0.50; bread = 2.00 }
}
It 'prices a known item' {
$catalog['apple'] | Should -Be 0.50
}
It 'knows the catalog size' {
$catalog.Count | Should -Be 2
}
}Variables defined in BeforeAll flow down into the tests. Note they do not flow upward or sideways — a variable created in a BeforeEach or inside one It won't be visible in AfterAll. Setup state is shared downward, not collected back up.
BeforeEach and AfterEach: a clean slate per test
BeforeAll runs once. BeforeEach runs before every It in its container, and AfterEach runs after every It. Use them when tests would otherwise pollute each other through shared mutable state:
Describe 'ShoppingCart' {
BeforeEach {
# Fresh cart before each test, so tests can't leak into one another
$script:cart = [System.Collections.Generic.List[string]]::new()
}
It 'starts empty' {
$cart.Count | Should -Be 0
}
It 'holds an item after adding one' {
$cart.Add('apple')
$cart.Count | Should -Be 1
}
}If you'd built the cart once in BeforeAll, the second test would inherit the 'apple' added by... well, whichever test ran first. BeforeEach guarantees isolation: each test gets its own fresh cart.
AfterAll and AfterEach: cleanup
AfterEach runs after each test; AfterAll runs once after all tests — even if a test failed. Use them to undo side effects: close a connection, remove a temp file, reset a variable.
Describe 'temporary marker file' {
BeforeAll {
$script:markerPath = Join-Path $TestDrive 'marker.txt'
'hello' | Set-Content -Path $script:markerPath
}
It 'created the file' {
Test-Path $markerPath | Should -BeTrue
}
AfterAll {
# Tidy up once, after every test in this Describe has run
if (Test-Path $markerPath) { Remove-Item $markerPath }
}
}($TestDrive is a Pester-provided sandbox path that's cleaned up automatically — a full topic in a later post.)
Try it yourself
Take the broken pattern from earlier and fix it. Create a file Greeter.ps1:
function Get-Greeting {
param([string]$Name)
"Hello, $Name!"
}Now write Greeter.Tests.ps1 with the dot-source at the top of the file (bare) and a test that calls Get-Greeting. Run it and watch the "command not found." Then move the dot-source into a BeforeAll and run it again — the test should pass. You've just felt the Discovery/Run boundary firsthand.
Common mistakes
Top-level code that you expect tests to use. Bare lines run in Discovery, before any test. Anything a test depends on — dot-sourcing, imports, building fixtures — belongs in
BeforeAll.Expecting
BeforeEachvariables inAfterAll. State flows down into tests, not back up into teardown. A variable born inBeforeEachor anItis gone byAfterAll. Capture whatAfterAllneeds inBeforeAllinstead.Expensive setup in
BeforeEach. If building the fixture is slow (a database connection, a large file) and the tests don't mutate it, useBeforeAllto do it once. ReserveBeforeEachfor state that must be reset between tests.
Recap
Pester 5 discovers your tests first, then runs them — two phases, in that order. Bare top-level code runs during Discovery, while It bodies and the Before*/After* blocks run during Run. Put setup in BeforeAll (once) or BeforeEach (per test), clean up in AfterAll/AfterEach, and remember that setup variables flow downward into your tests. Internalize the two-phase model and the rest of Pester stops being mysterious.
Next up: Part 11 — Arrange–Act–Assert and Naming Tests People Can Read, where good structure meets readable intent.