# Pester 6 Is Here


[Pester 6.0.0](https://github.com/pester/Pester/releases/tag/6.0.0) was released on July 7, roughly two years after the first 6.0 alpha. If you followed my [getting started with Pester](/2025-06-02-getting-started-with-pester/) post last year, everything you wrote then still runs — but there's a new way of writing assertions, a few behavior changes that can bite, and some genuinely useful additions.

<!--more-->

> **Update (September 2026):** two point releases followed — see [what Pester 6.1 brought](/2026-08-16-pester-v6-1/) and [what Pester 6.2 brought](/2026-09-14-pester-v6-2/). Note that 6.2 reverses one of the breaking changes below. And if you use the experimental parallel runs, [6.0.1](https://github.com/pester/Pester/releases/tag/6.0.1) (July 18) fixes an intermittent crash in them.

## A long road to 6.0

The first [6.0.0-alpha1](https://github.com/pester/Pester/releases/tag/6.0.0-alpha1) was published in June 2024, and alpha5 followed in October that year. Then things went quiet for about twenty months, until five release candidates were published in a single week at the end of June 2026, and GA followed on July 7. [Jakub Jareš](https://github.com/nohwnd) (nohwnd) published every one of them, with Frode Flaten ([fflaten](https://github.com/fflaten)) as co-maintainer.

The same day, [Pester 5.9.0](https://github.com/pester/Pester/releases/tag/5.9.0) moved v5 into maintenance mode: *"critical bug fixes and security updates only, no new features."*

**Supported versions:** Pester 6 runs on **Windows PowerShell 5.1** and **PowerShell 7.4 and later**. Windows PowerShell is *not* dropped — what's gone is PowerShell 3, 4, 6 and the early, unsupported 7.x versions.

## Installing it next to the inbox version

Windows 10 / Server 2016 and later ship Pester 3.4.0 inbox, and `Update-Module` can't update it, so you install the new version side by side.

On **Windows PowerShell 5.1** that still trips over the publisher check. The inbox 3.4.0 is signed by Microsoft, newer Pester versions are signed with a different certificate, and the built-in PowerShellGet refuses:

```powershell
Install-Module Pester -RequiredVersion 6.0.0 -Scope CurrentUser -Force
```

```text
PackageManagement\Install-Package : A Microsoft-signed module named 'Pester' with version '3.4.0' that was previously installed conflicts with the new module 'Pester' from publisher 'CN=DigiCert Trusted Root G4,
OU=www.digicert.com, O=DigiCert Inc, C=US' with version '6.0.0'. Installing the new module may result in system instability. If you still want to install or update, use -SkipPublisherCheck parameter.
At C:\Program Files\WindowsPowerShell\Modules\PowerShellGet\1.0.0.1\PSModule.psm1:1809 char:21
+ ...          $null = PackageManagement\Install-Package @PSBoundParameters
+                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (Microsoft.Power....InstallPackage:InstallPackage) [Install-Package], Exception
    + FullyQualifiedErrorId : PublishersMismatch,Validate-ModuleAuthenticodeSignature,Microsoft.PowerShell.PackageManagement.Cmdlets.InstallPackage
```

So on 5.1, add `-SkipPublisherCheck`, as the [install docs](https://pester.dev/docs/introduction/installation) tell you to:

```powershell
Install-Module Pester -RequiredVersion 6.0.0 -Scope CurrentUser -Force -SkipPublisherCheck
```

On **PowerShell 7** you can leave it off: PowerShell 7.6 installed Pester 6 without it for me, with 3.4.0 still in its inbox location.

All the examples below come from a small demo folder with a `run.ps1` helper that imports one exact Pester version and runs a test file with detailed output — that way you see what *that* version does, not whatever is newest on your machine.

## The new Should-* assertions

The headline feature: the [Assert](https://github.com/nohwnd/assert) project has been merged into Pester. Next to the classic `Should -Be` (with a space) you now get `Should-Be` (with a dash) and around 40 siblings: `Should-BeString`, `Should-BeCollection`, `Should-BeEquivalent`, `Should-HaveType`, `Should-Throw`, `Should-HaveParameter`, and so on.

**The classic syntax keeps working.** Nothing forces you to rewrite your tests. You adopt the new assertions where they help, and if you want to enforce the new style later, `Should.DisableV5 = $true` in your configuration makes the old syntax throw.

Here are the `Format-PersonName` tests from the [getting started post](/2025-06-02-getting-started-with-pester/), rewritten with the new assertions:

```powershell
Describe 'Format-PersonName' {
    It 'Should format names in standard order by default' {
        Format-PersonName -FirstName 'john' -LastName 'doe' | Should-BeString 'John Doe' -CaseSensitive
    }

    It 'Should format names with last name first when switch is used' {
        Format-PersonName -FirstName 'jane' -LastName 'smith' -LastNameFirst | Should-BeString 'Smith, Jane' -CaseSensitive
    }

    It 'Should handle extra spaces correctly' {
        Format-PersonName -FirstName '  bob  ' -LastName '  johnson  ' | Should-BeString 'Bob Johnson'
    }

    It 'Should have a mandatory FirstName parameter' {
        Get-Command Format-PersonName | Should-HaveParameter -ParameterName FirstName -Type ([string]) -Mandatory
    }
}
```

```powershell
pwsh -NoProfile -File ./run.ps1 -Version 6.0.0 -Path ./v6.0/Format-PersonName.Tests.ps1
```

```text
Pester v6.0.0

Running tests from 1 files.

Running tests from 'C:\code\pester-v6-demo\v6.0\Format-PersonName.Tests.ps1'
Describing Format-PersonName
  [+] Should format names in standard order by default 370ms
  [+] Should format names with last name first when switch is used 31ms
  [+] Should handle extra spaces correctly 33ms
  [+] Should have a mandatory FirstName parameter 142ms
Tests completed in 3.17s
Tests Passed: 4, Failed: 0, Skipped: 0, Inconclusive: 0, NotRun: 0
```

Two things worth knowing about the new family:

- **They're type-specific.** `Should-BeString` has string options (`-CaseSensitive`, `-TrimWhitespace`), collection assertions are separate from value assertions, and `Should-Be` refuses a collection on `-Expected` instead of guessing what you meant.
- **The failure messages are much better.** That's really the reason to switch. Here are three tests that fail on purpose:

```powershell
Describe 'Failure messages' {
    It 'compares strings case-sensitively' {
        'Hello' | Should-BeString 'hello' -CaseSensitive
    }

    It 'checks the type of a piped array' {
        [int[]](1, 2) | Should-HaveType ([int[]])
    }

    It 'compares nested objects' {
        $actual = [pscustomobject]@{
            Name    = 'Jane'
            Address = [pscustomobject]@{ City = 'Springfield' }
        }
        $actual | Should-BeEquivalent ([pscustomobject]@{
            Name    = 'Jane'
            Address = [pscustomobject]@{ City = 'Shelbyville' }
        })
    }
}
```

```powershell
pwsh -NoProfile -File ./run.ps1 -Version 6.0.0 -Path ./v6.0/Messages.Tests.ps1
```

```text
Pester v6.0.0

Running tests from 1 files.

Running tests from 'C:\code\pester-v6-demo\v6.0\Messages.Tests.ps1'
Describing Failure messages
  [-] compares strings case-sensitively 223ms
   Expected strings to be the same, but they were different.
   String lengths are both 5.
   Strings differ at index 0.
   Expected: 'hello'
   But was:  'Hello'
             ^
   at 'Hello' | Should-BeString 'hello' -CaseSensitive, C:\code\pester-v6-demo\v6.0\Messages.Tests.ps1:4
  [-] checks the type of a piped array 153ms
   Expected value to have type [int[]], but got [Object[]] @(1, 2).
   Hint: You piped a [int[]] into a type assertion, but the pipeline streams a multi-item collection and re-collects it as [Object[]], so the assertion saw [Object[]], not the [int[]] you piped. To assert the type of a collection, pass it as the -Actual argument instead of piping it, e.g. -Actual $value.
   at [int[]](1, 2) | Should-HaveType ([int[]]), C:\code\pester-v6-demo\v6.0\Messages.Tests.ps1:8
  [-] compares nested objects 207ms
   Expected and actual are not equivalent!
   Expected:
   PSObject{
       Address=PSObject{
           City='Shelbyville';
       };
       Name='Jane';
   }
   Actual:
   PSObject{
       Address=PSObject{
           City='Springfield';
       };
       Name='Jane';
   }
   Summary:
   Expected property .Address.City with value 'Shelbyville' to be equivalent to the actual value, but got 'Springfield'.
   at $actual | Should-BeEquivalent ([pscustomobject]@{, C:\code\pester-v6-demo\v6.0\Messages.Tests.ps1:16
Tests completed in 1.68s
Tests Passed: 0, Failed: 3, Skipped: 0, Inconclusive: 0, NotRun: 0
```

What stands out is the level of detail. The string failure doesn't just tell you the values differ: it gives you the lengths, the index and a caret under the first difference, so it's immediately obvious *why* it failed. And it doesn't stop at one level — the nested object comparison points straight at `.Address.City`. The hint on the piped array is very handy too; that one is a classic PowerShell pipeline gotcha.

## Should-BeEquivalent

`Should-BeEquivalent` deserves its own section. To be honest, it's a great reason to switch to v6 on its own — it's very, very powerful.

It compares two objects *deeply*: every property, nested objects and collections included, and tells you exactly which path differs. Two parameters make it practical for real objects:

- **`-ExcludePathsNotOnExpected`** — only compare the properties you put in the expected object, and ignore everything else on the actual one
- **`-ExcludePath`** — skip specific properties, like an ID or a timestamp that's different on every run

That replaces a lot of hand-written property-by-property comparisons. A user object with fixed values, and four tests against it:

```powershell
Describe 'Should-BeEquivalent' {
    BeforeAll {
        # Fixed values, so the output is the same on every run
        $user = [pscustomobject]@{
            Id      = '5f0c3b1e-2a4d-4c8e-9b7a-1d2e3f4a5b6c'
            Name    = 'Jane'
            Roles   = @('admin', 'user')
            Address = [pscustomobject]@{ City = 'Springfield'; Country = 'US' }
            Created = [datetime]'2026-07-01T09:00:00'
        }
    }

    It 'compares the whole object, nested properties included' {
        $user | Should-BeEquivalent ([pscustomobject]@{
            Id      = '5f0c3b1e-2a4d-4c8e-9b7a-1d2e3f4a5b6c'
            Name    = 'Jane'
            Roles   = @('admin', 'user')
            Address = [pscustomobject]@{ City = 'Springfield'; Country = 'US' }
            Created = [datetime]'2026-07-01T09:00:00'
        })
    }

    It 'only checks the properties you list' {
        $user | Should-BeEquivalent ([pscustomobject]@{
            Name    = 'Jane'
            Address = [pscustomobject]@{ City = 'Springfield' }
        }) -ExcludePathsNotOnExpected
    }

    It 'skips properties that differ on every run' {
        $user | Should-BeEquivalent ([pscustomobject]@{
            Id      = 'something else'
            Name    = 'Jane'
            Roles   = @('admin', 'user')
            Address = [pscustomobject]@{ City = 'Springfield'; Country = 'US' }
            Created = [datetime]'2000-01-01'
        }) -ExcludePath 'Id', 'Created'
    }

    It 'reports every difference at once (fails on purpose)' {
        $user | Should-BeEquivalent ([pscustomobject]@{
            Name    = 'John'
            Roles   = @('admin')
            Address = [pscustomobject]@{ City = 'Springfield'; Country = 'NL' }
        }) -ExcludePathsNotOnExpected
    }
}
```

```powershell
pwsh -NoProfile -File ./run.ps1 -Version 6.0.0 -Path ./v6.0/Equivalent.Tests.ps1
```

```text
Pester v6.0.0

Running tests from 1 files.

Running tests from 'C:\code\pester-v6-demo\v6.0\Equivalent.Tests.ps1'
Describing Should-BeEquivalent
  [+] compares the whole object, nested properties included 273ms
  [+] only checks the properties you list 18ms
  [+] skips properties that differ on every run 31ms
  [-] reports every difference at once (fails on purpose) 101ms
   Expected and actual are not equivalent!
   Expected:
   PSObject{
       Address=PSObject{
           City='Springfield';
           Country='NL';
       };
       Name='John';
       Roles=@('admin');
   }
   Actual:
   PSObject{
       Address=PSObject{
           City='Springfield';
           Country='US';
       };
       Created=07/01/2026 09:00:00;
       Id='5f0c3b1e-2a4d-4c8e-9b7a-1d2e3f4a5b6c';
       Name='Jane';
       Roles=@('admin', 'user');
   }
   Summary:
   Expected property .Name with value 'John' to be equivalent to the actual value, but got 'Jane'.
   Expected property .Roles with value 'admin' to be equivalent to the actual value, but got @('admin', 'user').
   Expected property .Address.Country with value 'NL' to be equivalent to the actual value, but got 'US'.
   Used options:
   Excluding all paths not found on Expected.
   at $user | Should-BeEquivalent ([pscustomobject]@{, C:\code\pester-v6-demo\v6.0\Equivalent.Tests.ps1:41
Tests completed in 1.36s
Tests Passed: 3, Failed: 1, Skipped: 0, Inconclusive: 0, NotRun: 0
```

Three differences, three lines in the summary, each with the full path to the property — including the nested `.Address.Country`. No more fixing one property, rerunning, and finding the next one. The "Used options" line at the end is a nice touch too: it tells you which exclusions were active, so you know what *wasn't* compared.

## Soft assertions

By default a test stops at the first failed assertion. Set `Should.ErrorAction = 'Continue'` and the new assertions keep going, reporting every failure in the test:

```powershell
Describe 'User object' {
    It 'has the expected shape' {
        $user = [pscustomobject]@{ Name = 'Jane'; Age = 30; City = 'Springfield' }

        $user.Name | Should-Be 'John'
        $user.Age  | Should-Be 31
        $user.City | Should-Be 'Springfield'
    }
}
```

Without and with soft assertions:

```powershell
pwsh -NoProfile -File ./run.ps1 -Version 6.0.0 -Path ./v6.0/SoftAssertions.Tests.ps1
pwsh -NoProfile -File ./run.ps1 -Version 6.0.0 -Path ./v6.0/SoftAssertions.Tests.ps1 -SoftAssert
```

```text
Pester v6.0.0

Running tests from 1 files.

Running tests from 'C:\code\pester-v6-demo\v6.0\SoftAssertions.Tests.ps1'
Describing User object
  [-] has the expected shape 193ms
   Expected [string] 'John', but got [string] 'Jane'.
   at $user.Name | Should-Be 'John', C:\code\pester-v6-demo\v6.0\SoftAssertions.Tests.ps1:6
Tests completed in 1.18s
Tests Passed: 0, Failed: 1, Skipped: 0, Inconclusive: 0, NotRun: 0
Pester v6.0.0

Running tests from 1 files.

Running tests from 'C:\code\pester-v6-demo\v6.0\SoftAssertions.Tests.ps1'
Describing User object
  [-] has the expected shape 199ms
   [0] Expected [string] 'John', but got [string] 'Jane'.
   at $user.Name | Should-Be 'John', C:\code\pester-v6-demo\v6.0\SoftAssertions.Tests.ps1:6
   [1] Expected [int] 31, but got [int] 30.
   at $user.Age  | Should-Be 31, C:\code\pester-v6-demo\v6.0\SoftAssertions.Tests.ps1:7
Tests completed in 986ms
Tests Passed: 0, Failed: 1, Skipped: 0, Inconclusive: 0, NotRun: 0
```

Handy when you're checking the shape of an object and want to fix everything in one go instead of one property per run.

## Mock call history

When `Should -Invoke` fails, Pester 6 now shows you the calls that *were* made, and which of them matched your filter:

```powershell
BeforeAll {
    function Send-Email ($To, $Subject) { }
}

Describe 'Order processing' {
    It 'emails alice exactly twice' {
        Mock Send-Email { }

        Send-Email -To 'alice@example.com' -Subject 'Welcome'
        Send-Email -To 'bob@example.com' -Subject 'Receipt'

        Should -Invoke Send-Email -Times 2 -Exactly -ParameterFilter { $To -eq 'alice@example.com' }
    }
}
```

```powershell
pwsh -NoProfile -File ./run.ps1 -Version 6.0.0 -Path ./v6.0/MockHistory.Tests.ps1
```

```text
Pester v6.0.0

Running tests from 1 files.

Running tests from 'C:\code\pester-v6-demo\v6.0\MockHistory.Tests.ps1'
Describing Order processing
  [-] emails alice exactly twice 871ms
   Expected Send-Email to be called 2 times exactly, but was called 1 times
   Performed invocations:
     [*] Send-Email -Subject 'Welcome' -To 'alice@example.com' from C:\code\pester-v6-demo\v6.0\MockHistory.Tests.ps1:7
     [ ] Send-Email -Subject 'Receipt' -To 'bob@example.com' from C:\code\pester-v6-demo\v6.0\MockHistory.Tests.ps1:7
   at Should -Invoke Send-Email -Times 2 -Exactly -ParameterFilter { $To -eq 'alice@example.com' }, C:\code\pester-v6-demo\v6.0\MockHistory.Tests.ps1:12
Tests completed in 2.04s
Tests Passed: 0, Failed: 1, Skipped: 0, Inconclusive: 0, NotRun: 0
```

If you've ever debugged a `ParameterFilter` by sprinkling `Write-Host` into a mock, you'll appreciate this one.

> **Update (October 2026):** notice that both calls say `MockHistory.Tests.ps1:7` — that's the line with `Mock Send-Email`, not the lines where `Send-Email` is actually called (9 and 10). That's a known bug, reported as [#3052](https://github.com/pester/Pester/issues/3052).

## Under the hood: discovery per file

The biggest change you *won't* see in your test code: Pester now discovers and runs **one file at a time**, instead of discovering everything first and then running everything. Mostly that's invisible, but if a test file relied on something another file set up during discovery (a variable, an imported module, a function defined outside `BeforeAll`), that no longer carries over. If your suite only passes when run as a whole and fails when you run a single file, that was already fragile — Pester 6 just makes it visible.

It's also what enables the next feature.

## Experimental: parallel runs

```powershell
$config = New-PesterConfiguration
$config.Run.Path = './tests'
$config.Run.Parallel = $true
Invoke-Pester -Configuration $config
```

Test files run in parallel, `Run.ParallelThrottleLimit` caps how many at once, and a `#pester:no-parallel` comment at the top of a file opts it out. In 6.0 it's PowerShell 7 only, and it falls back to sequential runs (with a warning) on Windows PowerShell 5.1 or when code coverage is enabled. It's marked experimental, so try it on a branch before you flip it on in your main pipeline.

## Code coverage changes

Coverage now uses the profiler-based tracer by default (faster than the old breakpoint approach — set `CodeCoverage.UseBreakpoints = $true` if you need the old numbers), and there's a new `Cobertura` output format next to JaCoCo. Cobertura is one of the formats the Azure DevOps [Publish Code Coverage Results](https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/publish-code-coverage-results-v2) task reads, so that may save you a conversion step.

## The breaking changes

The full list is in the [release notes](https://github.com/pester/Pester/releases/tag/6.0.0), and the [v5 to v6 migration guide](https://pester.dev/docs/migrations/v5-to-v6) has a symptom/fix entry for each. The ones most likely to bite:

- **`Assert-MockCalled` and `Assert-VerifiableMock` are gone** — use `Should -Invoke` and `Should -InvokeVerifiable`
- **The legacy v4 `Invoke-Pester` parameters are gone** (`-Script`, `-OutputFile`, `-EnableExit`, …) — switch to `New-PesterConfiguration`. Check your pipelines and build scripts for this one first
- **`Pending` and `-Focus` are removed**
- **Duplicate setup blocks throw** — two `BeforeAll` blocks in the same `Describe` is now an error (but see the update at the top: [6.2](/2026-09-14-pester-v6-2/) allows them again)
- **Hidden folders are searched for tests** — `.git` is still skipped, but other dot-folders are not
- **`None` is a reserved tag filter** — it now means "tests without tags"

Three behavior changes are easier to show than to describe. First, mocks no longer fall through to the real command when none of the parameter filters match, and `<...>` in a test name is now evaluated as a full expression:

```powershell
Describe 'Behavior changes in Pester 6' {
    # Pester 5 called the real Get-Date when no parameter filter matched; Pester 6 fails instead.
    It 'does not fall through to the real command' {
        Mock Get-Date { [datetime]'2000-01-01' } -ParameterFilter { $Year -eq 2000 }
        Get-Date -Year 2026
    }

    # Pester 5 printed the template literally; Pester 6 evaluates the expression.
    It 'adds up to <($a + $b)>' -ForEach @(@{ a = 1; b = 2 }) {
        $a + $b | Should-Be 3
    }
}
```

```powershell
pwsh -NoProfile -File ./run.ps1 -Version 6.0.0 -Path ./v6.0/Changes.Tests.ps1
```

```text
Pester v6.0.0

Running tests from 1 files.

Running tests from 'C:\code\pester-v6-demo\v6.0\Changes.Tests.ps1'
Describing Behavior changes in Pester 6
  [-] does not fall through to the real command 601ms
   RuntimeException: No mock for command 'Get-Date' matched the call: none of the parameter filters matched, and there is no default mock to fall back to. Add a default mock (e.g. `Mock Get-Date { ... }`) or adjust an existing -ParameterFilter.
   The following parameter filters were evaluated and did not match:
       { $Year -eq 2000 }  bound parameters: Year = 2026
  [+] adds up to 3 97ms
Tests completed in 1.78s
Tests Passed: 1, Failed: 1, Skipped: 0, Inconclusive: 0, NotRun: 0
```

The fix for the first one is to add a default `Mock` without a filter, so every call has somewhere to go.

Second, an empty `-ForEach` now fails discovery instead of silently running zero tests:

```powershell
$servers = @()

Describe 'Server <_>' -ForEach $servers {
    It 'responds to ping' {
        $true | Should-BeTrue
    }
}
```

```powershell
pwsh -NoProfile -File ./run.ps1 -Version 6.0.0 -Path ./v6.0/EmptyForEach.Tests.ps1
```

```text
Pester v6.0.0

Running tests from 1 files.
[-] Discovery in C:\code\pester-v6-demo\v6.0\EmptyForEach.Tests.ps1 failed with:
System.ArgumentException: 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 to allow it for the whole run. (Parameter 'ForEach')
at Describe, C:\Users\RobertPrüst\AppData\Local\powershell\Modules\Pester\6.0.0\Pester.psm1: line 15552
at <ScriptBlock>, C:\code\pester-v6-demo\v6.0\EmptyForEach.Tests.ps1: line 4
at <ScriptBlock>, C:\Users\RobertPrüst\AppData\Local\powershell\Modules\Pester\6.0.0\Pester.psm1: line 3274
at Invoke-File, C:\Users\RobertPrüst\AppData\Local\powershell\Modules\Pester\6.0.0\Pester.psm1: line 3283
at Invoke-BlockContainer, C:\Users\RobertPrüst\AppData\Local\powershell\Modules\Pester\6.0.0\Pester.psm1: line 3187
at Invoke-ContainerDiscovery, C:\Users\RobertPrüst\AppData\Local\powershell\Modules\Pester\6.0.0\Pester.psm1: line 1568
at Invoke-Test, C:\Users\RobertPrüst\AppData\Local\powershell\Modules\Pester\6.0.0\Pester.psm1: line 2678
at Invoke-Pester<End>, C:\Users\RobertPrüst\AppData\Local\powershell\Modules\Pester\6.0.0\Pester.psm1: line 5347
at <ScriptBlock>, C:\code\pester-v6-demo\run.ps1: line 24
Tests completed in 315ms
Tests Passed: 0, Failed: 0, Skipped: 0, Inconclusive: 0, NotRun: 0
Container failed: 1
  - C:\code\pester-v6-demo\v6.0\EmptyForEach.Tests.ps1
```

A data-driven test that quietly runs nothing because the data source came back empty is a green build that lies to you. If empty really is fine, add `-AllowNullOrEmptyForEach` to that block, or set `Run.FailOnNullOrEmptyForEach = $false` for the whole run.

> **Update (October 2026):** the error itself is clear, but it comes with a long stack trace through Pester's own internals — reported as [#3057](https://github.com/pester/Pester/issues/3057). Also note the summary line: `Tests Passed: 0` is shown in green, and the failure only shows up in the `Container failed` line below it.

The migration guide also points to two community agent skills in [awesome-copilot](https://github.com/github/awesome-copilot) (`pester-migration` and `pester-should-migration`) if you want an AI assistant to do the mechanical part. As always with that kind of thing: review the diff.

## Should you move yet?

My rule of thumb: if you're learning Pester or moving to a new version anyway, go for the latest. The move from 4.x to 5.x was the big syntax change. From 5.x to 6.x should be a lot easier: the classic `Should` syntax keeps working, so the work is mostly in the breaking changes listed above — v4-style `Invoke-Pester` calls, `Assert-MockCalled`, empty `-ForEach` blocks and mocks that relied on falling through. And with v5 now in maintenance mode, there's not much reason to wait.

## Wrap

There's a lot in Pester 6, but most of it is additive. Run your suite on it once, fix what the migration guide's symptom list tells you to fix, and adopt the new assertions at your own pace.

Happy testing 😊

