mirror of
https://github.com/github/awesome-copilot.git
synced 2026-07-14 01:51:02 +00:00
Add pester-migration skill (experimental) (#2163)
* Add pester-migration skill A self-contained, experimental skill that helps upgrade PowerShell Pester test suites across major versions (v3->v4, v4->v5, v5->v6). One router SKILL.md plus per-jump references. The v5->v6 guidance tracks Pester 6, which is still a release candidate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Regenerate skills index for pester-migration Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review feedback on pester-migration skill - Pin the stable-v5 install to -MaximumVersion 5.99.99 so it keeps installing v5 after Pester 6 goes GA (SKILL.md). - Make the baseline run command version-agnostic (bare Invoke-Pester) and note that parameters differ across majors (SKILL.md). - Replace the '->' mapping arrows inside powershell fences with comment + valid replacement lines so snippets are copy/paste-safe (SKILL.md v5->v6 cheat sheet, v5-to-v6.md, and v3-to-v4.md). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review: scope -SkipPublisherCheck, trim status callout, order references - Drop -SkipPublisherCheck from the default install commands; scope it to Windows PowerShell 5.1 (installing over the OS's Microsoft-signed built-in Pester 3) per pester.dev install docs, instead of an unconditional default. - Trim the experimental status callout: keep the preview marker, drop the date-based, human-oriented wording. - Reorder the References table into version progression order (v3->v4, v4->v5, v5->v6). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: nohwnd <jakub@jares.cz> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Aaron Powell <me@aaron-powell.com>
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
---
|
||||
name: pester-migration
|
||||
description: 'Experimental (preview) Pester migration skill for upgrading PowerShell Pester test suites across major versions — v3→v4, v4→v5, and v5→v6. The v5→v6 path tracks Pester 6, which is still a release candidate, so that guidance may change. 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.'
|
||||
---
|
||||
|
||||
# Pester Migration
|
||||
|
||||
> **Experimental / preview.** The **v5→v6** guidance tracks Pester 6 while it is a release
|
||||
> candidate and may change; verify against the current
|
||||
> [release notes](https://github.com/pester/Pester/releases). v3→v4 and v4→v5 cover stable releases.
|
||||
|
||||
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](references/v3-to-v4.md) | `Should Be` → `Should -Be`, `Contain` → `FileContentMatch`, `Assert-VerifiableMocks` → `Assert-VerifiableMock`, array-assertion edge cases. |
|
||||
| [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`. |
|
||||
| [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. |
|
||||
|
||||
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.
|
||||
|
||||
```powershell
|
||||
# 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](references/v3-to-v4.md) |
|
||||
| `$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) |
|
||||
| `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](references/v5-to-v6.md) |
|
||||
|
||||
Install the target version when ready:
|
||||
|
||||
```powershell
|
||||
# Latest stable v5 — pin the major so this keeps installing v5 even after v6 goes GA
|
||||
Install-Module Pester -MaximumVersion 5.99.99 -Force
|
||||
|
||||
# Pester 6 (currently a release candidate — needs -AllowPrerelease)
|
||||
Install-Module Pester -AllowPrerelease -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.
|
||||
|
||||
1. **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.
|
||||
```powershell
|
||||
# Bare Invoke-Pester works on every major; exact parameters differ
|
||||
# (v3/v4: -Script/-OutputFile; v5+/v6: -Path/-Output).
|
||||
Invoke-Pester
|
||||
```
|
||||
2. **Read the reference** for this jump (table above) so you know the full scope before editing.
|
||||
3. **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.
|
||||
4. **Switch versions** with `Install-Module` (Step 0), then re-import: `Remove-Module Pester;
|
||||
Import-Module Pester` (or start a fresh session).
|
||||
5. **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.
|
||||
6. **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)
|
||||
```powershell
|
||||
# 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](references/v4-to-v5.md).
|
||||
|
||||
### v5 → v6 (most common fixes)
|
||||
```powershell
|
||||
# 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](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.
|
||||
@@ -0,0 +1,92 @@
|
||||
# Migrating Pester v3 → v4
|
||||
|
||||
This is the smallest jump — mostly an assertion-syntax rename. Many suites need only minor changes,
|
||||
some need none. It is largely script-automatable, but always review the diff and re-run the suite.
|
||||
|
||||
Official guide: https://pester.dev/docs/migrations/v3-to-v4
|
||||
|
||||
> Heads-up: if your goal is a modern Pester (5 or 6), v3→v4 is only the first step. Do it, get
|
||||
> green, then continue with [v4-to-v5.md](v4-to-v5.md) and [v5-to-v6.md](v5-to-v6.md).
|
||||
|
||||
---
|
||||
|
||||
## Change 1 — Dashed `Should` assertion syntax
|
||||
|
||||
v4 introduced the parameter-style `Should` syntax. The bareword form still ran in v4 but is
|
||||
**removed in v5**, so converting now saves a later migration.
|
||||
|
||||
```powershell
|
||||
# v3 (bareword)
|
||||
It 'checks something' { 10 | Should Be 10 }
|
||||
|
||||
# v4+ (dashed)
|
||||
It 'checks something' { 10 | Should -Be 10 }
|
||||
```
|
||||
|
||||
The rename applies to every operator: `Be`, `BeExactly`, `Match`, `Throw`, `BeNullOrEmpty`,
|
||||
`Contain`, etc. → `-Be`, `-BeExactly`, `-Match`, `-Throw`, `-BeNullOrEmpty`, …
|
||||
|
||||
There is a well-known AST-based converter, `Update-PesterTest` (Chris Dent / Wojciech Sciesinski),
|
||||
that inserts the dashes safely by parsing the file rather than regexing it:
|
||||
https://gist.github.com/indented-automation/aeb14825e39dd8849beee44f681fbab3 — it's also reproduced
|
||||
in the official v3→v4 guide. Review its output, especially for non-UTF-8/ASCII files, where it can
|
||||
change encoding.
|
||||
|
||||
---
|
||||
|
||||
## Change 2 — `Contain` → `FileContentMatch`
|
||||
|
||||
The `Contain` assertion was renamed to `FileContentMatch` (it tests file **contents**, which the
|
||||
old name made ambiguous against collection containment).
|
||||
|
||||
```powershell
|
||||
# Should Contain -> Should -FileContentMatch
|
||||
# Should Not Contain -> Should -Not -FileContentMatch
|
||||
'app.config' | Should -FileContentMatch 'setting'
|
||||
'app.config' | Should -Not -FileContentMatch 'secret'
|
||||
```
|
||||
|
||||
A simple regex-based migration script from the official guide (verify results — it can produce
|
||||
false positives):
|
||||
|
||||
```powershell
|
||||
$content = Get-Content -Path $file -Encoding $encoding
|
||||
$content = $content -replace 'Should\s+\-?Contain', 'Should -FileContentMatch'
|
||||
$content = $content -replace 'Should\s+\-?Not\s*-?Contain', 'Should -Not -FileContentMatch'
|
||||
$content = $content -replace 'Assert-VerifiableMocks', 'Assert-VerifiableMock'
|
||||
$content | Set-Content -Path $file -Encoding $encoding
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Change 3 — `Assert-VerifiableMocks` → `Assert-VerifiableMock`
|
||||
|
||||
The cmdlet was renamed (dropped the trailing `s`). Rename all occurrences.
|
||||
|
||||
> In Pester 5 this is *deprecated* and in Pester 6 it is *removed* — when you continue past v4,
|
||||
> switch to `Should -InvokeVerifiable`. See [v5-to-v6.md](v5-to-v6.md).
|
||||
|
||||
---
|
||||
|
||||
## Change 4 — Array assertions (watch for edge cases)
|
||||
|
||||
`Should` gained array assertions in v4. This is transparent for most tests, but there are edge
|
||||
cases where an array test that passed under v3 fails under v4. If an array-related test changes
|
||||
result after the rename, inspect it manually rather than forcing it to pass. Background:
|
||||
https://github.com/pester/Pester/issues/873.
|
||||
|
||||
Mocking also shifted subtly when Pester moved from functions to aliases; there are no required
|
||||
changes, but if mocked-command behavior looks off, see
|
||||
https://github.com/pester/Pester/issues/810 and https://github.com/pester/Pester/issues/812.
|
||||
|
||||
---
|
||||
|
||||
## v3 → v4 checklist
|
||||
|
||||
- [ ] Suite runs on v3 first (baseline).
|
||||
- [ ] All `Should <Operator>` converted to `Should -<Operator>` (prefer the AST converter).
|
||||
- [ ] `Should Contain` → `Should -FileContentMatch` (and the `-Not` form).
|
||||
- [ ] `Assert-VerifiableMocks` → `Assert-VerifiableMock`.
|
||||
- [ ] Array-assertion behavior changes reviewed manually.
|
||||
- [ ] File encoding preserved by any scripted replacement.
|
||||
- [ ] Suite green on v4; diff reviewed; committed.
|
||||
@@ -0,0 +1,282 @@
|
||||
# Migrating Pester v4 → v5
|
||||
|
||||
This is the hard jump. v5 introduced a new runtime that splits a test run into two phases —
|
||||
**Discovery** and **Run** — and that changes how you must *structure* tests. It is not a pure
|
||||
find-and-replace. Read this whole file before editing a suite.
|
||||
|
||||
Official guide: https://pester.dev/docs/migrations/v4-to-v5 ·
|
||||
Breaking changes: https://pester.dev/docs/migrations/breaking-changes-in-v5
|
||||
|
||||
---
|
||||
|
||||
## The one concept that explains everything: Discovery and Run
|
||||
|
||||
A v5+ run happens in two passes:
|
||||
|
||||
- **Discovery** — Pester executes each `*.Tests.ps1` file top to bottom but only to *find* tests.
|
||||
It invokes the `Describe`/`Context` script blocks to collect the tree of `It`s, evaluates `It`
|
||||
`-Name` strings and `-TestCases`/`-ForEach` data, and records `BeforeAll`/`It`/etc. script blocks
|
||||
**without running them**.
|
||||
- **Run** — Pester then executes the recorded setups, tests, and teardowns with correct scoping.
|
||||
|
||||
**The two rules that make a suite v5-correct:**
|
||||
|
||||
1. Put **all** test code inside `It`, `BeforeAll`, `BeforeEach`, `AfterAll`, or `AfterEach`.
|
||||
2. Put **no** test code directly in `Describe`/`Context` bodies or at the top of the file —
|
||||
unless it is meant to build tests, in which case it goes in `BeforeDiscovery`.
|
||||
|
||||
Code that sits loose in a `Describe` body or at file top-level runs during **Discovery**, and its
|
||||
results are usually **not** available during **Run**. This is the root cause of most "it worked in
|
||||
v4, it's `$null` in v5" bugs.
|
||||
|
||||
---
|
||||
|
||||
## Fix 1 — Move file setup into `BeforeAll` and use `$PSScriptRoot`
|
||||
|
||||
The classic v4 header dot-sources the system-under-test at file scope using
|
||||
`$MyInvocation.MyCommand.Path`. Both the placement and that variable break in v5.
|
||||
|
||||
```powershell
|
||||
# BEFORE (v4)
|
||||
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace('.Tests.', '.')
|
||||
. "$here\$sut"
|
||||
|
||||
Describe 'Get-Cactus' {
|
||||
It 'Returns 🌵' { Get-Cactus | Should -Be '🌵' }
|
||||
}
|
||||
```
|
||||
|
||||
```powershell
|
||||
# AFTER (v5+)
|
||||
BeforeAll {
|
||||
# Do NOT use $MyInvocation.MyCommand.Path here.
|
||||
. $PSScriptRoot/Get-Cactus.ps1
|
||||
# or, by convention from the test file name:
|
||||
# . $PSCommandPath.Replace('.Tests.ps1', '.ps1')
|
||||
}
|
||||
|
||||
Describe 'Get-Cactus' {
|
||||
It 'Returns 🌵' { Get-Cactus | Should -Be '🌵' }
|
||||
}
|
||||
```
|
||||
|
||||
Why `$MyInvocation.MyCommand.Path` fails: it returns the path only when evaluated directly in the
|
||||
script body. Inside *any* function or script block (and `BeforeAll` is a script block) `Path` is
|
||||
empty. Use `$PSScriptRoot` (the test file's directory) or `$PSCommandPath` (the test file's full
|
||||
path) instead. `string.Replace('.Tests.ps1','.ps1')` is case-sensitive — keep the `.Tests.ps1`
|
||||
casing exact.
|
||||
|
||||
> `$MyInvocation.MyCommand.Path` is fine *inside your module/product code* — the change only
|
||||
> affects the test-file header pattern. See
|
||||
> https://pester.dev/docs/usage/importing-tested-functions#migrating-from-pester-v4.
|
||||
|
||||
There is a community migration script that does the BeforeAll wrap for you (review its output):
|
||||
https://gist.github.com/nohwnd/d488bd14ab4572f92ae77e208f476ada
|
||||
|
||||
---
|
||||
|
||||
## Fix 2 — Generate tests with `BeforeDiscovery` + `-ForEach`, not loose `foreach`
|
||||
|
||||
A very common v4 pattern builds tests from data with a top-level `foreach`. In v5 the data often
|
||||
isn't defined at Discovery time, so no tests get generated, or the per-item variable is missing
|
||||
inside `It`.
|
||||
|
||||
```powershell
|
||||
# BROKEN in v5: $files is set in BeforeAll (Run phase), but the foreach runs in Discovery
|
||||
BeforeAll { $files = Get-ChildItem *.ps1 }
|
||||
foreach ($file in $files) {
|
||||
Describe "$file is correct" {
|
||||
It 'has empty line at end' { }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Two things must change: build the data in `BeforeDiscovery` (so it exists during Discovery), and
|
||||
pass per-item data into the test with `-ForEach`/`-TestCases` (so `It` can see it during Run):
|
||||
|
||||
```powershell
|
||||
BeforeDiscovery {
|
||||
$files = Get-ChildItem *.ps1 # runs during Discovery
|
||||
}
|
||||
|
||||
Describe 'script <_> is correct' -ForEach $files {
|
||||
It 'has an empty line at the end' {
|
||||
# $_ is the current file here
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Prefer `-ForEach` on the block/`It` over a hand-written `foreach`; it both creates the copies and
|
||||
makes the current item available. Use `<_>` (or `<Name>` for hashtable items) in the name to
|
||||
template per-item titles. Reference: https://pester.dev/docs/usage/data-driven-tests.
|
||||
|
||||
---
|
||||
|
||||
## Fix 3 — `-Skip` and `-TestCases` are evaluated during Discovery
|
||||
|
||||
Because filters and data are resolved during Discovery, conditions computed in `BeforeAll` are not
|
||||
available yet.
|
||||
|
||||
```powershell
|
||||
# DOES NOT skip: $isSkipped is set in BeforeAll (Run), but -Skip is read in Discovery
|
||||
Describe 'd' {
|
||||
BeforeAll { $isSkipped = Get-IsSkipped }
|
||||
It 'i' -Skip:$isSkipped { }
|
||||
}
|
||||
```
|
||||
|
||||
Move cheap skip logic to file scope (it runs on every Discovery) or, better, base it on a static
|
||||
global like `$IsWindows`:
|
||||
|
||||
```powershell
|
||||
$isSkipped = -not $IsWindows
|
||||
Describe 'd' {
|
||||
It 'i' -Skip:$isSkipped { }
|
||||
}
|
||||
```
|
||||
|
||||
Keep Discovery-time code cheap — it runs every time the file is discovered, which can be often.
|
||||
|
||||
---
|
||||
|
||||
## Fix 4 — Variables don't leak from Discovery into the test
|
||||
|
||||
Variables defined during Discovery are **not** visible in `BeforeAll/-Each`, `AfterAll/-Each`, or
|
||||
`It`. If you compute something while generating tests and need it at run time, attach it to the
|
||||
test via `-ForEach`/`-TestCases`. (`TestDrive` is Run-only and likewise can't be used in
|
||||
`-ForEach`.)
|
||||
|
||||
---
|
||||
|
||||
## Fix 5 — `Should -Throw` matches with `-like` wildcards
|
||||
|
||||
In v5, `Should -Throw <message>` matches the exception message with `-like`, not `.Contains()`. A
|
||||
substring that used to match now needs wildcards.
|
||||
|
||||
```powershell
|
||||
# v4: matched a substring
|
||||
{ throw 'connection failed: timeout' } | Should -Throw 'timeout'
|
||||
# v5+: use wildcards to match part of the message
|
||||
{ throw 'connection failed: timeout' } | Should -Throw '*timeout*'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Fix 6 — Mocks: scoping, debugging, and `InModuleScope`
|
||||
|
||||
- **Scope follows placement.** In v5, mocks (and their call counts) are scoped to where you put
|
||||
them — the current block/test — not the entire `Describe`/`Context`. Define the `Mock` in the
|
||||
same `BeforeAll`/`It` scope where it applies, and assert counts in that scope.
|
||||
- **`Assert-VerifiableMocks` was removed.** Use `Should -InvokeVerifiable`. (`Assert-MockCalled`
|
||||
and `Assert-VerifiableMock` still exist but are *deprecated* in v5 and **removed in v6** — prefer
|
||||
`Should -Invoke` / `Should -InvokeVerifiable` now to save a second migration. See
|
||||
[v5-to-v6.md](v5-to-v6.md).)
|
||||
- **Mocks are debuggable.** v5 no longer rewrites your mock script block, so you can set
|
||||
breakpoints inside `-MockWith` and inside `-ParameterFilter`.
|
||||
- **Avoid `InModuleScope` around `Describe`/`It`.** It loads the module during Discovery (slowing
|
||||
it down) and lets you test internals instead of the published surface. Prefer `-ModuleName` on
|
||||
`Mock` and on `Should -Invoke`; if you must use `InModuleScope`, keep it inside `It`. See
|
||||
https://pester.dev/docs/usage/mocking.
|
||||
|
||||
```powershell
|
||||
# Prefer this over wrapping the whole Describe in InModuleScope
|
||||
Mock Get-Internal -ModuleName MyModule { 'mocked' }
|
||||
Should -Invoke Get-Internal -ModuleName MyModule -Times 1 -Exactly
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Fix 7 — `Invoke-Pester` parameters → `New-PesterConfiguration`
|
||||
|
||||
`Invoke-Pester`'s interface was overhauled. v5 kept a **deprecated** compatibility set so v4 calls
|
||||
mostly still run (with a warning), but you should move to either the **Simple** parameters or the
|
||||
**Advanced** `-Configuration` object. (v6 removes the legacy set entirely — migrate now.)
|
||||
|
||||
**Simple interface** (parameter → config property):
|
||||
|
||||
| Simple parameter | Configuration property |
|
||||
|---|---|
|
||||
| `-Path` | `Run.Path` |
|
||||
| `-ExcludePath` | `Run.ExcludePath` |
|
||||
| `-Tag` | `Filter.Tag` |
|
||||
| `-ExcludeTag` | `Filter.ExcludeTag` |
|
||||
| `-FullNameFilter` | `Filter.FullName` |
|
||||
| `-Output` | `Output.Verbosity` |
|
||||
| `-CI` | `TestResult.Enabled` + `Run.Exit` (both `$true`) |
|
||||
| `-PassThru` | `Run.PassThru` |
|
||||
|
||||
**Legacy (v4) parameters → config:**
|
||||
|
||||
| v4 parameter | Configuration property |
|
||||
|---|---|
|
||||
| `-Script` | `Run.Path` (paths only — no hashtables) |
|
||||
| `-EnableExit` | `Run.Exit` |
|
||||
| `-TestName` | replaced by `-FullNameFilter` / `Filter.FullName` |
|
||||
| `-CodeCoverage` | `CodeCoverage.Path` (+ `CodeCoverage.Enabled = $true`) |
|
||||
| `-CodeCoverageOutputFile` | `CodeCoverage.OutputPath` |
|
||||
| `-CodeCoverageOutputFileEncoding` | `CodeCoverage.OutputEncoding` |
|
||||
| `-CodeCoverageOutputFileFormat` | `CodeCoverage.OutputFormat` |
|
||||
| `-OutputFile` | `TestResult.OutputPath` (+ `TestResult.Enabled = $true`) |
|
||||
| `-OutputFormat` | `TestResult.OutputFormat` |
|
||||
| `-Show` / `-Output` | `Output.Verbosity` (see mapping below) |
|
||||
| `-PesterOption`, `-Strict` | ignored / not available |
|
||||
|
||||
`-Show` value → `Output.Verbosity`: `All`/`Default`/`Detailed` → `Detailed`; `Fails`/`Normal` →
|
||||
`Normal`; `Diagnostic` → `Diagnostic`; `Minimal` → `Minimal`; `None` → `None`.
|
||||
|
||||
```powershell
|
||||
# BEFORE (v4 legacy)
|
||||
Invoke-Pester -Script ./tests -CodeCoverage ./src/*.ps1 `
|
||||
-OutputFile result.xml -OutputFormat NUnitXml -EnableExit
|
||||
|
||||
# AFTER (v5 Advanced)
|
||||
$config = New-PesterConfiguration
|
||||
$config.Run.Path = './tests'
|
||||
$config.Run.Exit = $true
|
||||
$config.CodeCoverage.Enabled = $true
|
||||
$config.CodeCoverage.Path = './src'
|
||||
$config.TestResult.Enabled = $true
|
||||
$config.TestResult.OutputPath = 'result.xml'
|
||||
$config.TestResult.OutputFormat = 'NUnitXml'
|
||||
Invoke-Pester -Configuration $config
|
||||
```
|
||||
|
||||
`-Output Diagnostic` is your best friend while migrating — it shows Discovery/Skip/Mock decisions.
|
||||
|
||||
---
|
||||
|
||||
## The new result object
|
||||
|
||||
The v5 result object is much richer and is what Pester uses internally. To keep a v4-era CI
|
||||
pipeline working, convert it with `ConvertTo-Pester4Result`. For NUnit output use
|
||||
`ConvertTo-NUnitReport`, or pass `-CI` to enable NUnit output, code coverage, and a failing exit
|
||||
code in one switch. Each test's `-TestCases`/`-ForEach` item is available on the test object's
|
||||
`Data` property.
|
||||
|
||||
---
|
||||
|
||||
## Other removed / changed things in v5
|
||||
|
||||
- **PowerShell 2** is no longer supported.
|
||||
- **Legacy `Should Be`** (no dash) is removed — convert to `Should -Be` ([v3-to-v4.md](v3-to-v4.md)).
|
||||
- **Gherkin** was removed — stay on Pester v4 if you need it.
|
||||
- `-Output`/`-Show` reduced to `None`, `Normal`, `Detailed`, `Diagnostic`.
|
||||
- `-TestName` → `-FullNameFilter`; `-Script` → `-Path` (paths only); `-PesterOption` removed.
|
||||
|
||||
---
|
||||
|
||||
## v4 → v5 checklist
|
||||
|
||||
- [ ] Suite runs green on v4 first (baseline).
|
||||
- [ ] File import moved into `BeforeAll`; `$MyInvocation.MyCommand.Path` replaced with
|
||||
`$PSScriptRoot`/`$PSCommandPath`.
|
||||
- [ ] No loose code in `Describe`/`Context` bodies or at file top-level; test-generating code moved
|
||||
to `BeforeDiscovery`.
|
||||
- [ ] `foreach`-generated tests converted to `-ForEach`; per-item data passed via `-ForEach`/`-TestCases`.
|
||||
- [ ] `-Skip:` conditions don't depend on `BeforeAll` variables.
|
||||
- [ ] `Should -Throw` messages use `-like` wildcards (`*...*`).
|
||||
- [ ] Mocks defined in the right scope; `Assert-VerifiableMocks` → `Should -InvokeVerifiable`;
|
||||
`InModuleScope` removed from around `Describe`/`It` in favor of `-ModuleName`.
|
||||
- [ ] `Invoke-Pester` call converted to Simple params or `New-PesterConfiguration`.
|
||||
- [ ] Suite green on v5 with `-Output Detailed`; diff reviewed; committed.
|
||||
@@ -0,0 +1,226 @@
|
||||
# Migrating Pester v5 → v6
|
||||
|
||||
Pester 6 builds on v5 and is **largely backwards-compatible** — most v5 suites run on v6 with no
|
||||
changes, and your existing `Should -Be` assertions keep working. The work is fixing a handful of
|
||||
previously-deprecated behaviors that now throw. This is a low-to-medium effort jump.
|
||||
|
||||
> Pester 6 is currently a **release candidate**. Install with
|
||||
> `Install-Module Pester -AllowPrerelease -Force` (add `-SkipPublisherCheck` on Windows
|
||||
> PowerShell 5.1). Some details may still change before final — check the release notes:
|
||||
> https://github.com/pester/Pester/releases.
|
||||
|
||||
Official guide: https://pester.dev/docs/migrations/v5-to-v6
|
||||
|
||||
---
|
||||
|
||||
## Quick upgrade checklist
|
||||
|
||||
For most suites this is low risk. Run through these, then read the details for anything that bites:
|
||||
|
||||
1. Run on **Windows PowerShell 5.1** or **PowerShell 7.4+** (older PS is dropped).
|
||||
2. Check any `-ForEach`/`-TestCases` that can be empty — it now **throws** unless you add
|
||||
`-AllowNullOrEmptyForEach`.
|
||||
3. Remove duplicate `BeforeAll`/`BeforeEach`/`AfterAll`/`AfterEach` in the same block.
|
||||
4. Replace `Assert-MockCalled` / `Assert-VerifiableMock` with `Should -Invoke` /
|
||||
`Should -InvokeVerifiable`.
|
||||
5. Add a default `Mock` where some calls don't match a `-ParameterFilter` — mocks no longer fall
|
||||
through to the real command.
|
||||
6. If you call `Invoke-Pester` with v4-style params (`-Script`, `-OutputFile`, …), switch to
|
||||
`New-PesterConfiguration`.
|
||||
|
||||
---
|
||||
|
||||
## Breaking changes (symptom → fix)
|
||||
|
||||
### PowerShell 5.1 and 7.4+ only
|
||||
PowerShell 3, 4, 6, and early/unsupported 7 are dropped (all out of support from Microsoft), which
|
||||
let Pester move its C# to .NET 8 (net462 for Windows PowerShell 5.1).
|
||||
|
||||
- **Symptom:** Pester won't import on an older PowerShell.
|
||||
- **Fix:** Update your machines and CI agents to Windows PowerShell 5.1 or PowerShell 7.4+.
|
||||
|
||||
### Discovery and Run now happen per file
|
||||
In v5 the run had two global phases: discover **every** file, then run every file. In v6 the unit
|
||||
of work is a single file — Pester discovers a file and runs it before moving to the next. This is
|
||||
what enables the experimental parallel runner, and serial runs follow the same model.
|
||||
|
||||
- **Symptom:** a file that relied on *another* file's discovery-time side effect fails — e.g. a
|
||||
module imported at the top of one file, a global variable, a changed working directory, or
|
||||
`-ForEach` data defined in a different file.
|
||||
- **Fix:** make each test file self-contained. Do discovery-time setup in `BeforeDiscovery`, and
|
||||
import the modules the file needs in its own `BeforeAll`. For setup every file needs, use
|
||||
`Run.BeforeContainer` (config) or a `Pester.BeforeContainer.ps1` at the repo root.
|
||||
|
||||
```powershell
|
||||
BeforeDiscovery {
|
||||
$cases = Get-Content "$PSScriptRoot/cases.json" | ConvertFrom-Json
|
||||
}
|
||||
Describe 'MyModule' {
|
||||
BeforeAll { Import-Module "$PSScriptRoot/MyModule.psm1" }
|
||||
It 'handles <Name>' -ForEach $cases { Invoke-Thing $Name | Should -Be 'ok' }
|
||||
}
|
||||
```
|
||||
|
||||
On-screen output also changes: one `Running tests from N files.` banner, per-file results, then a
|
||||
single grand total. The old `Starting discovery in N files.` / `Discovery found X tests` framing is
|
||||
gone for a normal run. The parallel runner (`Run.Parallel`) keeps the same result object with a few
|
||||
edge cases — see https://pester.dev/docs/usage/result-object#parallel-runner-edge-cases.
|
||||
|
||||
### Empty or `$null` `-ForEach` throws
|
||||
`-ForEach` (or `-TestCases`) given `$null` or `@()` now throws instead of silently skipping. This
|
||||
catches the common bug of pointing `-ForEach` at a variable that wasn't defined in
|
||||
`BeforeDiscovery`, or external data that failed to load.
|
||||
|
||||
- **Symptom:**
|
||||
```
|
||||
Value can not be null or empty array. If this is expected, use -AllowNullOrEmptyForEach
|
||||
on this Describe, or set the Run.FailOnNullOrEmptyForEach configuration option to $false ...
|
||||
```
|
||||
- **Fix:** when the data can legitimately be empty, allow it on that specific block/test:
|
||||
```powershell
|
||||
Describe 'Optional cases' -ForEach $cases -AllowNullOrEmptyForEach {
|
||||
It 'runs only when there is data' { }
|
||||
}
|
||||
```
|
||||
You *can* disable the check for the whole run with `Run.FailOnNullOrEmptyForEach = $false`, but
|
||||
that brings back the silent skipping it's meant to catch — prefer fixing the data or using
|
||||
`-AllowNullOrEmptyForEach` where empty is genuinely expected.
|
||||
|
||||
### Duplicate setup/teardown blocks throw
|
||||
A block may have only one of each `BeforeAll`/`BeforeEach`/`AfterAll`/`AfterEach`. Two of the same
|
||||
(a common copy-paste bug) was silently allowed in v5; v6 throws.
|
||||
|
||||
- **Symptom:** `BeforeAll is already defined in this block. Each block can only have one BeforeAll.`
|
||||
- **Fix:** combine them into one block:
|
||||
```powershell
|
||||
Describe 'd' {
|
||||
BeforeAll {
|
||||
$a = 1
|
||||
$b = 2 # was a second BeforeAll
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Test names evaluate `<...>` templates as expressions
|
||||
In v6 the content of every `<...>` token in a `Describe`/`Context`/`It` name is evaluated as a
|
||||
PowerShell expression in the test's run scope (current `-ForEach` item and its properties, in-scope
|
||||
variables, arithmetic, method calls). Everything outside `<...>` is kept literal. In v5 only simple
|
||||
data/variable/property references were substituted.
|
||||
|
||||
- **Symptom:** a name containing expression-like content inside `<...>` that used to render
|
||||
literally now evaluates.
|
||||
```powershell
|
||||
# v5 renders literally: "adds up to <($a + $b)>"; v6 evaluates: "adds up to 3"
|
||||
It 'adds up to <($a + $b)>' -ForEach @(@{ a = 1; b = 2 }) { }
|
||||
```
|
||||
- **Fix (to keep the literal text):** escape the leading `<` with a backtick:
|
||||
```powershell
|
||||
It 'adds up to `<($a + $b)`>' -ForEach @(@{ a = 1; b = 2 }) { }
|
||||
```
|
||||
|
||||
### `Assert-MockCalled` and `Assert-VerifiableMock` removed
|
||||
Deprecated in v5, removed in v6.
|
||||
|
||||
- **Symptom:** `The term 'Assert-MockCalled' is not recognized ...`
|
||||
- **Fix:**
|
||||
```powershell
|
||||
# Assert-MockCalled -> Should -Invoke
|
||||
# Assert-VerifiableMock -> Should -InvokeVerifiable
|
||||
Should -Invoke Get-Thing -Times 1 -Exactly
|
||||
Should -InvokeVerifiable
|
||||
```
|
||||
|
||||
### Mocks no longer fall through to the real command
|
||||
In v5, a call to a mocked command that matched none of your `-ParameterFilter` mocks quietly ran
|
||||
the **real** command. v6 removes that implicit fall-through.
|
||||
|
||||
- **Symptom:**
|
||||
```
|
||||
No mock for command 'Get-Thing' matched the call: none of the parameter filters matched,
|
||||
and there is no default mock to fall back to. Add a default mock ...
|
||||
```
|
||||
- **Fix:** add a default mock (no `-ParameterFilter`) for the unfiltered calls, or widen a filter:
|
||||
```powershell
|
||||
Mock Get-Thing -MockWith { 'default' } # everything else
|
||||
Mock Get-Thing -ParameterFilter { $Name -eq 'a' } -MockWith { 'a' } # special case
|
||||
```
|
||||
|
||||
### `Set-ItResult -Pending` removed
|
||||
`Pending` was never fully implemented in v5 and is gone.
|
||||
|
||||
- **Symptom:** `Parameter set cannot be resolved using the specified named parameters.`
|
||||
- **Fix:** use `-Inconclusive` or `-Skipped`, or mark the test with `It … -Skip`:
|
||||
```powershell
|
||||
Set-ItResult -Inconclusive -Because 'not implemented yet'
|
||||
```
|
||||
|
||||
### Code coverage uses the Profiler tracer by default
|
||||
Coverage no longer sets a breakpoint on every command; it uses the Profiler's tracer, which is much
|
||||
faster on large code bases. `CodeCoverage.UseBreakpoints` is no longer experimental and defaults to
|
||||
`$false`.
|
||||
|
||||
- **Symptom:** coverage numbers differ from v5 and you want the old behavior.
|
||||
- **Fix:** `$config.CodeCoverage.UseBreakpoints = $true`.
|
||||
|
||||
### `CodeCoverage.OutputFormat = 'CoverageGutters'` removed
|
||||
All coverage output is now relative to the repo root (`Run.RepoRoot`, found from the `.git`
|
||||
directory), so plain `JaCoCo` already works with the Coverage Gutters extension.
|
||||
|
||||
- **Symptom:** setting `OutputFormat` to `'CoverageGutters'` throws an invalid-value error.
|
||||
- **Fix:** use `JaCoCo` (default) or `Cobertura`.
|
||||
|
||||
### `Invoke-Pester` legacy (v4) parameters removed
|
||||
Only the **Simple** set (`-Path`, `-Output`, `-Container`, `-Tag`, …) and the **Advanced** set
|
||||
(`-Configuration`) remain. The v4-style parameter set is gone.
|
||||
|
||||
- **Symptom:** calls like `Invoke-Pester -Script … -OutputFile … -OutputFormat … -EnableExit
|
||||
-CodeCoverage …` fail with a parameter-binding error.
|
||||
- **Fix:** use a configuration object (the full parameter→config map is in the
|
||||
"`Invoke-Pester` parameters → `New-PesterConfiguration`" section of [v4-to-v5.md](v4-to-v5.md)):
|
||||
```powershell
|
||||
$config = New-PesterConfiguration
|
||||
$config.Run.Path = './tests'
|
||||
$config.Run.Exit = $true
|
||||
$config.TestResult.Enabled = $true
|
||||
$config.TestResult.OutputPath = 'result.xml'
|
||||
$config.TestResult.OutputFormat = 'NUnitXml'
|
||||
$config.CodeCoverage.Enabled = $true
|
||||
$config.CodeCoverage.Path = './src'
|
||||
Invoke-Pester -Configuration $config
|
||||
```
|
||||
|
||||
> Config convenience in v6: setting **any** non-default option in the `TestResult` or
|
||||
> `CodeCoverage` section auto-enables that section, so you no longer have to set `.Enabled = $true`
|
||||
> separately for a report to be written.
|
||||
|
||||
---
|
||||
|
||||
## New `Should-*` assertions — optional, NOT part of upgrading
|
||||
|
||||
v6 adds a new family of assertions — `Should-Be`, `Should-Throw`, `Should-Invoke`, and ~40 more
|
||||
(note the hyphen: `Should-Be`, a distinct command, vs. the classic `Should -Be`) — with clearer
|
||||
failure messages. **You do not need to touch your existing `Should -Be` assertions to upgrade**;
|
||||
they keep working. Treat any `Should -Be` → `Should-Be` rewrite as a separate, later effort, not
|
||||
part of this migration. Reference: https://pester.dev/docs/commands/Should-Be.
|
||||
|
||||
If you do try them: the new assertions take the actual value from the pipeline or `-Actual`. The
|
||||
pipeline unwraps input (so `@(1)` becomes `1`, `@()` becomes `$null`, and a collection is
|
||||
re-collected as `[object[]]`, losing types like `[int[]]`). Use `-Actual` when you need the exact
|
||||
value or concrete collection type.
|
||||
|
||||
---
|
||||
|
||||
## v5 → v6 checklist
|
||||
|
||||
- [ ] Suite runs green on v5 first (baseline).
|
||||
- [ ] Running on Windows PowerShell 5.1 or PowerShell 7.4+.
|
||||
- [ ] `-ForEach`/`-TestCases` that can be empty marked `-AllowNullOrEmptyForEach` (or data fixed).
|
||||
- [ ] Duplicate `Before*`/`After*` blocks combined.
|
||||
- [ ] `Assert-MockCalled` → `Should -Invoke`; `Assert-VerifiableMock` → `Should -InvokeVerifiable`.
|
||||
- [ ] Default `Mock` added wherever calls can miss every `-ParameterFilter`.
|
||||
- [ ] `Set-ItResult -Pending` → `-Inconclusive`/`-Skipped`.
|
||||
- [ ] `<...>` test-name templates that should stay literal are backtick-escaped.
|
||||
- [ ] Each test file is self-contained (no reliance on another file's discovery-time state).
|
||||
- [ ] `Invoke-Pester` legacy params → `New-PesterConfiguration`.
|
||||
- [ ] Coverage `OutputFormat` is `JaCoCo`/`Cobertura`; `UseBreakpoints` set only if old numbers needed.
|
||||
- [ ] Suite green on v6; diff reviewed; committed.
|
||||
Reference in New Issue
Block a user