What Pester 6.2 Brought

Pester 6.2.0 was released on September 9 — the third feature release in the Pester 6 line in just over two months. The headline is setup files that apply per folder, but a few of the smaller changes might matter more to you day to day, including one that reverses a 6.0 breaking change.
This continues from Pester 6.0 and Pester 6.1. Same demo folder and run.ps1 helper as before, now pinned to 6.2.0:
Install-Module -Name Pester -RequiredVersion 6.2.0 -Scope CurrentUser -ForceSetup files that cascade down the folder tree
Pester 6 already supported a Pester.BeforeContainer.ps1 file in the root of your repository, for setup that every test file needs (importing the module under test, loading helper functions). In 6.2 that becomes a cascade: every Pester.BeforeContainer.ps1 from the repository root down to the test file’s folder applies, outermost first. Think of it like .editorconfig.
The demo repository looks like this:
repo/
├── Pester.BeforeContainer.ps1 <- imports src/MyModule.psm1, applies to everything
├── src/MyModule.psm1
└── tests/
├── Pester.BeforeContainer.ps1 <- defines a New-TestUser helper, applies to tests/ and below
├── unit/
│ ├── Pester.BeforeContainer.ps1 <- $script:Db = 'in-memory'
│ └── Get-User.Tests.ps1
├── integration/
│ ├── Pester.BeforeContainer.ps1 <- $script:Db = 'real-sql'
│ └── Get-User.Tests.ps1
└── docs/
├── Pester.BeforeContainer.ps1 <- #pester:no-inherit, $script:Db = 'none'
└── Docs.Tests.ps1Each setup file is just a BeforeAll block, for example the root one:
# Applies to every test file under this folder.
BeforeAll {
Import-Module $PSScriptRoot/src/MyModule.psm1 -Force
}And the unit tests can use everything above them without a single line of setup of their own:
Describe 'Get-User (unit)' {
It 'uses the in-memory database' {
$script:Db | Should-BeString 'in-memory'
}
It 'can use helpers from tests/ and the module from the root' {
(Get-User -Name (New-TestUser -Name 'jane').Name).Name | Should-BeString 'jane'
}
}Three details that make this work well:
- Setup is scoped to the file — it’s dot-sourced into each test file’s own scope, so the unit and integration folders can set the same variable to different values without leaking into each other.
#pester:no-inheritat the top of a setup file stops the cascade at that folder, likeroot = truein.editorconfig. Thedocsfolder above uses it.- Each container reports which files applied in a new
BeforeContainerFileproperty, which is handy when you’re wondering why a test does or doesn’t see something.
The cascade starts at Run.RepoRoot, which Pester finds by looking for a .git folder. The demo isn’t a git repository, so the helper script sets it explicitly (run.ps1 resolves -RepoRoot to an absolute path, because a relative one isn’t resolved correctly):
pwsh -NoProfile -File ./run.ps1 -Version 6.2.0 -Path ./v6.2/repo/tests -RepoRoot ./v6.2/repo
pwsh -NoProfile -File ./v6.2/show-cascade.ps1Pester v6.2.0
Running tests from 3 files.
Running tests from 'C:\code\pester-v6-demo\v6.2\repo\tests\docs\Docs.Tests.ps1'
Describing Docs (opted out with #pester:no-inherit)
[+] does not see the root setup 5.04s
Running tests from 'C:\code\pester-v6-demo\v6.2\repo\tests\integration\Get-User.Tests.ps1'
Describing Get-User (integration)
[+] uses the real database 19ms
Running tests from 'C:\code\pester-v6-demo\v6.2\repo\tests\unit\Get-User.Tests.ps1'
Describing Get-User (unit)
[+] uses the in-memory database 22ms
[+] can use helpers from tests/ and the module from the root 18ms
Tests completed in 6.65s
Tests Passed: 4, Failed: 0, Skipped: 0, Inconclusive: 0, NotRun: 0
Docs.Tests.ps1 [Passed]
C:\code\pester-v6-demo\v6.2\repo\tests\docs\Pester.BeforeContainer.ps1
Get-User.Tests.ps1 [Passed]
C:\code\pester-v6-demo\v6.2\repo\Pester.BeforeContainer.ps1
C:\code\pester-v6-demo\v6.2\repo\tests\Pester.BeforeContainer.ps1
C:\code\pester-v6-demo\v6.2\repo\tests\integration\Pester.BeforeContainer.ps1
Get-User.Tests.ps1 [Passed]
C:\code\pester-v6-demo\v6.2\repo\Pester.BeforeContainer.ps1
C:\code\pester-v6-demo\v6.2\repo\tests\Pester.BeforeContainer.ps1
C:\code\pester-v6-demo\v6.2\repo\tests\unit\Pester.BeforeContainer.ps1The request for this came from Johan Ljunggren (johlju) for the SqlServerDsc test suite, where it’s estimated to remove around a thousand lines of duplicated setup. If you maintain a module with a few dozen test files that all start with the same BeforeAll, this is the reason to update.
Watch out: top-level code in a Pester.BeforeContainer.ps1 now only runs during discovery. If your root setup file has a bare Import-Module line, wrap it in BeforeAll { }, or it won’t be there when your tests run.
Multiple BeforeAll blocks are allowed again
Pester 6.0 made duplicate setup and teardown blocks in the same Describe an error. 6.2 reverses that: you can have several BeforeAll, AfterAll, BeforeEach and AfterEach blocks. Setups run in order, teardowns in reverse order.
Describe 'Multiple setup blocks' {
BeforeAll { $script:steps = @('first') }
BeforeAll { $script:steps += 'second' }
AfterAll { Write-Host 'teardown: first registered' }
AfterAll { Write-Host 'teardown: second registered' }
It 'ran both BeforeAll blocks in order' {
$script:steps | Should-BeCollection @('first', 'second')
}
}On 6.1 and on 6.2:
pwsh -NoProfile -File ./run.ps1 -Version 6.1.0 -Path ./v6.2/checks/Setup.Tests.ps1
pwsh -NoProfile -File ./run.ps1 -Version 6.2.0 -Path ./v6.2/checks/Setup.Tests.ps1Pester v6.1.0
Running tests from 1 files.
[-] Discovery in C:\code\pester-v6-demo\v6.2\checks\Setup.Tests.ps1 failed with:
System.Management.Automation.RuntimeException: BeforeAll is already defined in this block. Each block can only have one BeforeAll. Combine the code into a single BeforeAll block.
at New-OneTimeTestSetup, C:\Users\RobertPrüst\AppData\Local\powershell\Modules\Pester\6.1.0\Pester.psm1: line 1403
at BeforeAll, C:\Users\RobertPrüst\AppData\Local\powershell\Modules\Pester\6.1.0\Pester.psm1: line 22898
at <ScriptBlock>, C:\code\pester-v6-demo\v6.2\checks\Setup.Tests.ps1: line 4
at New-Block, C:\Users\RobertPrüst\AppData\Local\powershell\Modules\Pester\6.1.0\Pester.psm1: line 789
at Describe, C:\Users\RobertPrüst\AppData\Local\powershell\Modules\Pester\6.1.0\Pester.psm1: line 16137
at <ScriptBlock>, C:\code\pester-v6-demo\v6.2\checks\Setup.Tests.ps1: line 2
at <ScriptBlock>, C:\Users\RobertPrüst\AppData\Local\powershell\Modules\Pester\6.1.0\Pester.psm1: line 3363
at Invoke-File, C:\Users\RobertPrüst\AppData\Local\powershell\Modules\Pester\6.1.0\Pester.psm1: line 3372
at Invoke-BlockContainer, C:\Users\RobertPrüst\AppData\Local\powershell\Modules\Pester\6.1.0\Pester.psm1: line 3297
at Invoke-ContainerDiscovery, C:\Users\RobertPrüst\AppData\Local\powershell\Modules\Pester\6.1.0\Pester.psm1: line 1535
at Invoke-Test, C:\Users\RobertPrüst\AppData\Local\powershell\Modules\Pester\6.1.0\Pester.psm1: line 2708
at Invoke-Pester<End>, C:\Users\RobertPrüst\AppData\Local\powershell\Modules\Pester\6.1.0\Pester.psm1: line 5570
at <ScriptBlock>, C:\code\pester-v6-demo\run.ps1: line 29
Tests completed in 425ms
Tests Passed: 0, Failed: 0, Skipped: 0, Inconclusive: 0, NotRun: 0
Container failed: 1
- C:\code\pester-v6-demo\v6.2\checks\Setup.Tests.ps1
Pester v6.2.0
Running tests from 1 files.
Running tests from 'C:\code\pester-v6-demo\v6.2\checks\Setup.Tests.ps1'
Describing Multiple setup blocks
[+] ran both BeforeAll blocks in order 113ms
teardown: second registered
teardown: first registered
Tests completed in 916ms
Tests Passed: 1, Failed: 0, Skipped: 0, Inconclusive: 0, NotRun: 0At the time of writing, the migration guide still lists duplicate blocks as something you need to fix when moving from v5. On 6.2 you don’t.
Configuration validation
Two changes that will save you some head-scratching:
- Values of the wrong type now throw.
Run.Parallel = 'yes'used to be accepted; now you get an error that tells you a bool was expected. - Unknown keys produce a warning. A typo like
Run.Paralelwas silently ignored in 6.0 and 6.1, so the option just didn’t do anything. NowInvoke-Pestertells you about it.
The demo script makes both mistakes, passing the configuration as a hashtable:
'--- wrong type'
try { Invoke-Pester -Configuration @{ Run = @{ Path = $tests; Parallel = 'yes' } } } catch { $_.Exception.Message }
'--- typo in a key'
$null = Invoke-Pester -Configuration @{ Run = @{ Path = $tests; Paralel = $true }; Output = @{ Verbosity = 'None' } }pwsh -NoProfile -File ./v6.2/checks/config-validation.ps1 -Version 6.2.0Pester 6.2.0
--- wrong type
Cannot process argument transformation on parameter 'Configuration'. Cannot convert value "System.Collections.Hashtable" to type "PesterConfiguration". Error: "Run.Parallel expects a bool, but got the string 'yes'."
--- typo in a key
WARNING: Ignoring configuration key 'Run.Paralel', there is no such option. Check the spelling, 'Get-Help about_PesterConfiguration' lists all the options.Better failure output
Should-BeString now shows every region that differs between two strings, with line numbers, instead of only the first difference. Useful when you compare generated files. It only does this for longer strings — more than 10 lines or 120 characters. Shorter strings keep the familiar view with a caret under the first difference, which for a short string is the more precise of the two.
Describe 'Should-BeString diff' {
It 'shows every differing region' {
$expected = @(
'name: demo', 'version: 1.0.0', 'author: Jane', 'license: MIT', 'description: A demo module',
'tags:', ' - pester', ' - testing', 'dependencies:', ' - name: PSReadLine', ' version: 2.4.5',
' - name: Pester', ' version: 6.1.0'
) -join "`n"
$actual = @(
'name: demo', 'version: 1.0.1', 'author: Jane', 'license: MIT', 'description: A demo module',
'tags:', ' - pester', ' - testing', 'dependencies:', ' - name: PSReadLine', ' version: 2.4.5',
' - name: Pester', ' version: 6.2.0'
) -join "`n"
$actual | Should-BeString $expected
}
}pwsh -NoProfile -File ./run.ps1 -Version 6.2.0 -Path ./v6.2/checks/Diff.Tests.ps1Pester v6.2.0
Running tests from 1 files.
Running tests from 'C:\code\pester-v6-demo\v6.2\checks\Diff.Tests.ps1'
Describing Should-BeString diff
[-] shows every differing region 215ms
Expected strings to be the same, but they were different.
String lengths are both 197.
Expected 13 line(s), actual 13 line(s).
2 regions differ.
1 1 | name: demo
2 | - version: 1.0.0
2 | + version: 1.0.1
3 3 | author: Jane
4 4 | license: MIT
...
11 11 | version: 2.4.5
12 12 | - name: Pester
13 | - version: 6.1.0
13 | + version: 6.2.0
at $actual | Should-BeString $expected, C:\code\pester-v6-demo\v6.2\checks\Diff.Tests.ps1:14
Tests completed in 1.19s
Tests Passed: 0, Failed: 1, Skipped: 0, Inconclusive: 0, NotRun: 0And when a BeforeAll throws, you now get the actual error and the line it came from, instead of a misleading message about break or continue:
Describe 'Setup that throws' {
BeforeAll { throw 'the database is down' }
It 'never runs' { }
}pwsh -NoProfile -File ./run.ps1 -Version 6.2.0 -Path ./v6.2/checks/Throws.Tests.ps1Pester v6.2.0
Running tests from 1 files.
Running tests from 'C:\code\pester-v6-demo\v6.2\checks\Throws.Tests.ps1'
Describing Setup that throws
[-] Describe Setup that throws failed
RuntimeException: the database is down
at <ScriptBlock>, C:\code\pester-v6-demo\v6.2\checks\Throws.Tests.ps1:2
Tests completed in 940ms
Tests Passed: 0, Failed: 1, Skipped: 0, Inconclusive: 0, NotRun: 0
BeforeAll \ AfterAll failed: 1
- Setup that throwsAssertions that passed while checking nothing
Should-BeFasterThan and Should-BeSlowerThan expect a script block to measure or a timespan to compare. In 6.1 they quietly passed when you gave them anything else — a string, a number, $null. Now they fail:
Describe 'Timing assertions' {
It 'rejects a string' {
'a string' | Should-BeFasterThan 1s
}
}pwsh -NoProfile -File ./run.ps1 -Version 6.1.0 -Path ./v6.2/checks/Strict.Tests.ps1
pwsh -NoProfile -File ./run.ps1 -Version 6.2.0 -Path ./v6.2/checks/Strict.Tests.ps1Pester v6.1.0
Running tests from 1 files.
Running tests from 'C:\code\pester-v6-demo\v6.2\checks\Strict.Tests.ps1'
Describing Timing assertions
[+] rejects a string 165ms
Tests completed in 1.14s
Tests Passed: 1, Failed: 0, Skipped: 0, Inconclusive: 0, NotRun: 0
Pester v6.2.0
Running tests from 1 files.
Running tests from 'C:\code\pester-v6-demo\v6.2\checks\Strict.Tests.ps1'
Describing Timing assertions
[-] rejects a string 180ms
Expected a [scriptblock] to measure or a [timespan] to compare, but got [string] 'a string'.
at 'a string' | Should-BeFasterThan 1s, C:\code\pester-v6-demo\v6.2\checks\Strict.Tests.ps1:4
Tests completed in 1.01s
Tests Passed: 0, Failed: 1, Skipped: 0, Inconclusive: 0, NotRun: 0If any of your tests start failing on this after the update, they weren’t testing anything before.
Parallel runs on Windows PowerShell 5.1
Parallel runs are still experimental, but they no longer need PowerShell 7: 6.2 switched from ForEach-Object -Parallel to a runspace pool, which works on both editions. To try it on 5.1:
powershell.exe -NoProfile -File ./run.ps1 -Version 6.2.0 -Path ./v6.2/repo/tests -RepoRoot ./v6.2/repo -ParallelWindows PowerShell 5.1 - Pester 6.2.0
Pester v6.2.0
Running tests from 3 files in parallel.
Running tests from 'C:\code\pester-v6-demo\v6.2\repo\tests\docs\Docs.Tests.ps1'
Describing Docs (opted out with #pester:no-inherit)
[+] does not see the root setup 6.67s
Running tests from 'C:\code\pester-v6-demo\v6.2\repo\tests\integration\Get-User.Tests.ps1'
Describing Get-User (integration)
[+] uses the real database 111ms
Running tests from 'C:\code\pester-v6-demo\v6.2\repo\tests\unit\Get-User.Tests.ps1'
Describing Get-User (unit)
[+] uses the in-memory database 111ms
[+] can use helpers from tests/ and the module from the root 7ms
Tests completed in 9.48s
Tests Passed: 4, Failed: 0, Skipped: 0, Inconclusive: 0, NotRun: 0Parallel runs also no longer silently lose a test file’s results: if a worker fails, that file is reported as failed and the rest of the run continues. The underlying cause of that failure, a race while importing modules in parallel, isn’t fixed yet — and it’s openly described as such in the release notes.
Fixes worth knowing
- Pester imports again on early PowerShell 7.4 patch releases — 6.0 and 6.1 failed there with a
PesterConfigurationDeserializererror (#3013). Invoke-Pesterkeeps its-1exit code on internal failures, which matters if your pipeline checks it.- The VS Code Test Explorer’s discovery run now applies the setup file cascade too.
Update (October 2026): a few issues were reported after this post, mostly around parallel runs. Worth keeping an eye on:
- #3047 — parallel runs can fail with “Collection was modified” during module import
- #3044 — warning output now shows up in parallel runs
- #3054 — a relative
Run.RepoRootisn’t resolved against the current location, so the setup file cascade doesn’t apply. Use an absolute path- #3059 —
Should-BeCollectiondoesn’t check item order. A migration gotcha: in v5,Should -Be @(1, 2, 3)did check order- #3060 —
Invoke-Pesteroverwrites the caller’s$pand$dvariables (this one goes back to 5.2)
Wrap
Three releases in, Pester 6 is settling down. If you’re starting the move from v5 now, go straight to 6.2 — same rule as always, go for the latest unless it’s broken: the setup file cascade removes a lot of boilerplate, the configuration validation catches typos, and one of the more annoying 6.0 breaking changes is gone again.
Happy testing 😊