Pester Migration
Pester is the test framework for PowerShell. Test files end in *.Tests.ps1 and use
Describe / Context / It blocks with Should assertions. This skill upgrades an existing
suite from one major Pester version to the next and gets it green again.
Mental model: each major jump has a different character. v3→v4 is mostly a syntax
rename. v4→v5 is a fundamental runtime change (the Discovery/Run split) and is the hard
one. v5→v6 is largely backwards-compatible — a handful of previously-deprecated things now
throw. Migrate one major at a time; never skip a version.
Detailed, symptom-driven guides live in references/ — load the one(s) for the jump you are doing.
References
| Reference |
When to load |
| v3-to-v4.md |
Should Be → Should -Be, Contain → FileContentMatch, Assert-VerifiableMocks → Assert-VerifiableMock, array-assertion edge cases. |
| v4-to-v5.md |
The big one. Discovery/Run phases, BeforeAll setup, $PSScriptRoot, BeforeDiscovery, -ForEach, mock scoping, Should -Throw wildcards, Invoke-Pester → New-PesterConfiguration. |
| v5-to-v6.md |
PowerShell 5.1/7.4+ only, per-file discovery+run, empty -ForEach throws, duplicate setup blocks throw, name <...> templates evaluate, Assert-MockCalled removed, mocks no longer fall through, code-coverage tracer, legacy Invoke-Pester params removed. |
Canonical source: the official migration guides at https://pester.dev/docs/migrations/ — this skill
mirrors them. When in doubt, prefer the website.
Step 0 — Detect where you are and where you're going
Find the installed version(s) and the version the tests were written for. These can differ.
# Installed Pester version(s) on this machine
Get-Module Pester -ListAvailable | Select-Object Name, Version, Path
# Version currently imported in the session
(Get-Module Pester).Version
Tell the source version from the test code with these heuristics:
You see in *.Tests.ps1 / build scripts |
Suite was written for |
Should Be / Should Contain (no dash) |
v3 or earlier → start at v3-to-v4 |
$MyInvocation.MyCommand.Path + dot-source at the top of the file; arbitrary code directly under Describe |
v4 → v4-to-v5 |
Assert-MockCalled, Assert-VerifiableMock, Set-ItResult -Pending |
v4 / early-v5 (these are removed in v6) |
Invoke-Pester -Script … -OutputFile … -CodeCoverage … (legacy params) |
v4 invocation → map to config |
BeforeAll { . $PSScriptRoot/… }, New-PesterConfiguration, Should -Invoke |
already v5-style → v5-to-v6 |
Install the target version when ready:
# Latest stable v5 — pin the major to avoid installing Pester 6
Install-Module Pester -MaximumVersion 5.99.99 -Force
# Pester 6
Install-Module Pester -Force
On Windows PowerShell 5.1 the OS ships a Microsoft-signed built-in Pester 3 that PowerShellGet
won't overwrite with the differently-signed newer Pester — add -SkipPublisherCheck there to
install side-by-side. Not needed on PowerShell 7+. See
https://pester.dev/docs/introduction/installation.
Migration workflow
Run this loop for each major jump. Do not jump two majors at once — go v4→v5, then v5→v6.
- Baseline. Run the suite on the current version first and record pass/fail. You need a
known-good (or known) starting point so you can tell migration regressions apart from
pre-existing failures.
# Bare Invoke-Pester works on every major; exact parameters differ
# (v3/v4: -Script/-OutputFile; v5+/v6: -Path/-Output).
Invoke-Pester
- Read the reference for this jump (table above) so you know the full scope before editing.
- Edit file by file. Apply the mechanical changes (see per-jump cheat sheets below and in the
reference). Keep changes small and reviewable — one file or one concern at a time.
- Switch versions with
Install-Module (Step 0), then re-import: Remove-Module Pester; Import-Module Pester (or start a fresh session).
- Run and fix. Re-run with
-Output Detailed; use -Output Diagnostic (v4→v5) or read the
explicit v6 error messages to locate problems. Match each failure to the symptom → fix
tables in the reference.
- Green, diff, commit. Re-run until the result matches the baseline (or better). Review the
diff, then commit. Migrating in small commits makes regressions trivial to bisect.
What actually changes (scope per jump)
| Jump |
Difficulty |
Nature |
| v3 → v4 |
Low |
Assertion-syntax rename (Should -Be). Largely script-automatable. |
| v4 → v5 |
High |
New two-phase runtime. Test structure changes: setup must move into BeforeAll, discovery-time code into BeforeDiscovery, file location via $PSScriptRoot. Not a pure find-replace. |
| v5 → v6 |
Low–Medium |
Backwards-compatible runtime; deprecated features now throw. Mostly small, targeted fixes. Your Should -Be assertions keep working unchanged. |
Quick cheat sheets
v4 → v5 (most common fixes)
# 1. Move file import into BeforeAll, use $PSScriptRoot (NOT $MyInvocation.MyCommand.Path)
# BEFORE
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
. "$here\Get-Thing.ps1"
# AFTER
BeforeAll { . $PSScriptRoot/Get-Thing.ps1 }
# 2. Any code that DISCOVERS/generates tests must be in BeforeDiscovery
BeforeDiscovery { $cases = Get-Content $PSScriptRoot/cases.json | ConvertFrom-Json }
# 3. Should -Throw matches with -like wildcards, not .Contains
{ throw 'a long message' } | Should -Throw '*long*'
# 4. Invoke-Pester legacy params → New-PesterConfiguration (see reference for full map)
Full details, scoping rules, and the parameter→config table: references/v4-to-v5.md.
v5 → v6 (most common fixes)
# 1. Mock assertions: removed verbs — rename (old -> new):
# Assert-MockCalled -> Should -Invoke
# Assert-VerifiableMock -> Should -InvokeVerifiable
Should -Invoke Get-Thing -Times 1 -Exactly
Should -InvokeVerifiable
# 2. Add a default mock — unmatched calls no longer run the real command
Mock Get-Thing { 'default' }
Mock Get-Thing -ParameterFilter { $Name -eq 'a' } -MockWith { 'a' }
# 3. Empty/$null -ForEach now throws; allow it only where empty is expected
Describe 'Optional' -ForEach $cases -AllowNullOrEmptyForEach { }
# 4. Combine duplicate BeforeAll/BeforeEach/AfterAll/AfterEach in the same block into one
Full breaking-change list with symptoms and fixes: references/v5-to-v6.md.
Safety rules
- Tests are the spec. Migration must not change what a test asserts — only how the suite is
structured and invoked. If a test starts passing/failing differently for any reason other than a
documented breaking change, investigate before accepting it.
- Automated migration scripts produce false positives. The community scripts (linked in the
references) help with
Should syntax and dot-sourcing, but always review the diff and re-run the
suite afterward. Never bulk-edit and commit unchecked.
- Mind file encoding when scripting replacements over
*.Tests.ps1 — preserve the original
encoding (UTF-8 vs ASCII) so you don't mangle non-ASCII test names.
- Work on a branch, commit per file/concern. Small commits keep
git bisect useful if a
migrated test goes red later.
1---2name: pester-migration3description: Pester migration skill for upgrading PowerShell Pester test suites across major versions — v3→v4, v4→v5, and v5→v6. Covers the Discovery/Run two-phase model, moving setup into BeforeAll, $PSScriptRoot vs $MyInvocation, mock changes (Assert-MockCalled → Should -Invoke, removed fall-through), Invoke-Pester parameters → PesterConfiguration, data-driven -ForEach/-TestCases, and the v6 breaking changes. Use when the user asks to upgrade, migrate, or modernize Pester tests, fix *.Tests.ps1 files that broke after bumping the Pester version, or convert legacy Should / Invoke-Pester syntax.4---5
6# Pester Migration
7
8Pester is the test framework for PowerShell. Test files end in `*.Tests.ps1` and use
9`Describe` / `Context` / `It` blocks with `Should` assertions. This skill upgrades an existing
10suite from one major Pester version to the next and gets it green again.
11
12> **Mental model:** each major jump has a different character. **v3→v4** is mostly a syntax
13> rename. **v4→v5** is a *fundamental runtime change* (the Discovery/Run split) and is the hard
14> one. **v5→v6** is largely backwards-compatible — a handful of previously-deprecated things now
15> throw. Migrate **one major at a time**; never skip a version.
16
17Detailed, symptom-driven guides live in `references/` — load the one(s) for the jump you are doing.
18
19## References
20
21| Reference | When to load |
22|---|---|
23| [v3-to-v4.md](references/v3-to-v4.md) | `Should Be` → `Should -Be`, `Contain` → `FileContentMatch`, `Assert-VerifiableMocks` → `Assert-VerifiableMock`, array-assertion edge cases. |
24| [v4-to-v5.md](references/v4-to-v5.md) | The big one. Discovery/Run phases, `BeforeAll` setup, `$PSScriptRoot`, `BeforeDiscovery`, `-ForEach`, mock scoping, `Should -Throw` wildcards, `Invoke-Pester` → `New-PesterConfiguration`. |
25| [v5-to-v6.md](references/v5-to-v6.md) | PowerShell 5.1/7.4+ only, per-file discovery+run, empty `-ForEach` throws, duplicate setup blocks throw, name `<...>` templates evaluate, `Assert-MockCalled` removed, mocks no longer fall through, code-coverage tracer, legacy `Invoke-Pester` params removed. |
26
27Canonical source: the official migration guides at https://pester.dev/docs/migrations/ — this skill
28mirrors them. When in doubt, prefer the website.
29
30## Step 0 — Detect where you are and where you're going
31
32Find the installed version(s) and the version the **tests** were written for. These can differ.
33
34```powershell
35# Installed Pester version(s) on this machine
36Get-Module Pester -ListAvailable | Select-Object Name, Version, Path
37
38# Version currently imported in the session
39(Get-Module Pester).Version
40```
41
42Tell the source version from the **test code** with these heuristics:
43
44| You see in `*.Tests.ps1` / build scripts | Suite was written for |
45|---|---|
46| `Should Be` / `Should Contain` (no dash) | v3 or earlier → start at [v3-to-v4](references/v3-to-v4.md) |
47| `$MyInvocation.MyCommand.Path` + dot-source at the **top** of the file; arbitrary code directly under `Describe` | v4 → [v4-to-v5](references/v4-to-v5.md) |
48| `Assert-MockCalled`, `Assert-VerifiableMock`, `Set-ItResult -Pending` | v4 / early-v5 (these are **removed in v6**) |
49| `Invoke-Pester -Script … -OutputFile … -CodeCoverage …` (legacy params) | v4 invocation → map to config |
50| `BeforeAll { . $PSScriptRoot/… }`, `New-PesterConfiguration`, `Should -Invoke` | already v5-style → [v5-to-v6](references/v5-to-v6.md) |
51
52Install the target version when ready:
53
54```powershell
55# Latest stable v5 — pin the major to avoid installing Pester 6
56Install-Module Pester -MaximumVersion 5.99.99 -Force
57
58# Pester 6
59Install-Module Pester -Force
60```
61
62> On **Windows PowerShell 5.1** the OS ships a Microsoft-signed built-in Pester 3 that PowerShellGet
63> won't overwrite with the differently-signed newer Pester — add `-SkipPublisherCheck` there to
64> install side-by-side. Not needed on PowerShell 7+. See
65> https://pester.dev/docs/introduction/installation.
66
67## Migration workflow
68
69Run this loop for each major jump. **Do not jump two majors at once** — go v4→v5, then v5→v6.
70
711. **Baseline.** Run the suite on the **current** version first and record pass/fail. You need a
72 known-good (or known) starting point so you can tell migration regressions apart from
73 pre-existing failures.
74 ```powershell
75 # Bare Invoke-Pester works on every major; exact parameters differ
76 # (v3/v4: -Script/-OutputFile; v5+/v6: -Path/-Output).
77 Invoke-Pester
78 ```
792. **Read the reference** for this jump (table above) so you know the full scope before editing.
803. **Edit file by file.** Apply the mechanical changes (see per-jump cheat sheets below and in the
81 reference). Keep changes small and reviewable — one file or one concern at a time.
824. **Switch versions** with `Install-Module` (Step 0), then re-import: `Remove-Module Pester;
83 Import-Module Pester` (or start a fresh session).
845. **Run and fix.** Re-run with `-Output Detailed`; use `-Output Diagnostic` (v4→v5) or read the
85 explicit v6 error messages to locate problems. Match each failure to the **symptom → fix**
86 tables in the reference.
876. **Green, diff, commit.** Re-run until the result matches the baseline (or better). Review the
88 diff, then commit. Migrating in small commits makes regressions trivial to bisect.
89
90## What actually changes (scope per jump)
91
92| Jump | Difficulty | Nature |
93|---|---|---|
94| v3 → v4 | Low | Assertion-syntax rename (`Should -Be`). Largely script-automatable. |
95| v4 → v5 | **High** | New two-phase runtime. Test **structure** changes: setup must move into `BeforeAll`, discovery-time code into `BeforeDiscovery`, file location via `$PSScriptRoot`. Not a pure find-replace. |
96| v5 → v6 | Low–Medium | Backwards-compatible runtime; deprecated features now throw. Mostly small, targeted fixes. Your `Should -Be` assertions keep working unchanged. |
97
98## Quick cheat sheets
99
100### v4 → v5 (most common fixes)
101```powershell
102# 1. Move file import into BeforeAll, use $PSScriptRoot (NOT $MyInvocation.MyCommand.Path)
103# BEFORE
104$here = Split-Path -Parent $MyInvocation.MyCommand.Path
105. "$here\Get-Thing.ps1"
106# AFTER
107BeforeAll { . $PSScriptRoot/Get-Thing.ps1 }
108
109# 2. Any code that DISCOVERS/generates tests must be in BeforeDiscovery
110BeforeDiscovery { $cases = Get-Content $PSScriptRoot/cases.json | ConvertFrom-Json }
111
112# 3. Should -Throw matches with -like wildcards, not .Contains
113{ throw 'a long message' } | Should -Throw '*long*'
114
115# 4. Invoke-Pester legacy params → New-PesterConfiguration (see reference for full map)
116```
117Full details, scoping rules, and the parameter→config table: [references/v4-to-v5.md](references/v4-to-v5.md).
118
119### v5 → v6 (most common fixes)
120```powershell
121# 1. Mock assertions: removed verbs — rename (old -> new):
122# Assert-MockCalled -> Should -Invoke
123# Assert-VerifiableMock -> Should -InvokeVerifiable
124Should -Invoke Get-Thing -Times 1 -Exactly
125Should -InvokeVerifiable
126
127# 2. Add a default mock — unmatched calls no longer run the real command
128Mock Get-Thing { 'default' }
129Mock Get-Thing -ParameterFilter { $Name -eq 'a' } -MockWith { 'a' }
130
131# 3. Empty/$null -ForEach now throws; allow it only where empty is expected
132Describe 'Optional' -ForEach $cases -AllowNullOrEmptyForEach { }
133
134# 4. Combine duplicate BeforeAll/BeforeEach/AfterAll/AfterEach in the same block into one
135```
136Full breaking-change list with symptoms and fixes: [references/v5-to-v6.md](references/v5-to-v6.md).
137
138## Safety rules
139
140- **Tests are the spec.** Migration must not change what a test asserts — only how the suite is
141 structured and invoked. If a test starts passing/failing differently for any reason other than a
142 documented breaking change, investigate before accepting it.
143- **Automated migration scripts produce false positives.** The community scripts (linked in the
144 references) help with `Should` syntax and dot-sourcing, but always review the diff and re-run the
145 suite afterward. Never bulk-edit and commit unchecked.
146- **Mind file encoding** when scripting replacements over `*.Tests.ps1` — preserve the original
147 encoding (UTF-8 vs ASCII) so you don't mangle non-ASCII test names.
148- **Work on a branch, commit per file/concern.** Small commits keep `git bisect` useful if a
149 migrated test goes red later.