# What Pester 6.1 Brought


Five weeks after [Pester 6.0](/2026-07-14-pester-v6-released/), [Pester 6.1.0](https://github.com/pester/Pester/releases/tag/6.1.0) was released on August 11. It's a point release in name, but it fills the obvious gap in the new assertions: writing your own `Should-*` assertions that behave like the built-in ones.

<!--more-->

If you haven't read the [6.0 post](/2026-07-14-pester-v6-released/) yet, start there — this one assumes you know what the new `Should-*` assertions are. The next point release has [its own post too](/2026-09-14-pester-v6-2/).

Supported versions are unchanged: Windows PowerShell 5.1 and PowerShell 7.4 and later. The examples below use the same demo folder and `run.ps1` helper as the 6.0 post, pinned to 6.1.0:

```powershell
Install-Module -Name Pester -RequiredVersion 6.1.0 -Scope CurrentUser -Force
```

## Write your own assertions with New-ShouldAssertion

In Pester 5 you could add custom assertions to `Should -<Operator>` through `Add-ShouldOperator`. For the new assertion style, 6.1 adds `New-ShouldAssertion`: a helper you call from inside your own function, and it takes care of the pipeline handling, value formatting, `-Because`, soft assertions and hints, so your assertion behaves exactly like `Should-Be` and friends.

Sticking with the `Format-PersonName` function from the [getting started post](/2025-06-02-getting-started-with-pester/), here's a `Should-BeTitleCase` assertion:

```powershell
function Should-BeTitleCase {
    [CmdletBinding()]
    param (
        [Parameter(Position = 0, ValueFromPipeline)] $Actual,
        [string] $Because
    )

    $assert = New-ShouldAssertion -Caller $PSCmdlet -Actual $Actual -Buffer $Input
    $Actual = $assert.Actual()
    $expected = (Get-Culture).TextInfo.ToTitleCase("$Actual".ToLower())

    if ($Actual -cne $expected) {
        $assert.Fail('Expected <expected>,<because> but got <actual>.', @{ Expected = $expected; Because = $Because })
    }
}
```

The pattern is always the same: create the assertion object, get the actual value from it (it handles piped input versus `-Actual`), do your check, and call `Fail()` with a message template. The `<expected>`, `<because>` and `<actual>` placeholders are filled in and formatted the way Pester formats its own messages.

Used in a test, one passing and one failing on purpose:

```powershell
Describe 'Format-PersonName' {
    It 'returns a title-cased name' {
        Format-PersonName -FirstName 'ALICE' -LastName 'brown' | Should-BeTitleCase
    }

    It 'fails with a readable message (on purpose)' {
        'alice Brown' | Should-BeTitleCase -Because 'names should be title case'
    }
}
```

```powershell
pwsh -NoProfile -File ./run.ps1 -Version 6.1.0 -Path ./v6.1/CustomAssertion.Tests.ps1
```

```text
Pester v6.1.0

Running tests from 1 files.

Running tests from 'C:\code\pester-v6-demo\v6.1\CustomAssertion.Tests.ps1'
Describing Format-PersonName
  [+] returns a title-cased name 129ms
  [-] fails with a readable message (on purpose) 159ms
   Expected 'Alice Brown', because names should be title case, but got 'alice Brown'.
   at 'alice Brown' | Should-BeTitleCase -Because 'names should be title case', C:\code\pester-v6-demo\v6.1\CustomAssertion.Tests.ps1:27
Tests completed in 1.67s
Tests Passed: 1, Failed: 1, Skipped: 0, Inconclusive: 0, NotRun: 0
```

One thing to know if you put assertions like this in a module: `Should` isn't an approved PowerShell verb, so exporting `Should-*` functions gives you the unapproved-verb warning on `Import-Module`. The release notes recommend naming the function `Assert-*` and exporting a `Should-*` alias for it:

```powershell
Set-Alias -Name Should-BeTitleCase -Value Assert-BeTitleCase
Export-ModuleMember -Function Assert-BeTitleCase -Alias Should-BeTitleCase
```

## Sharper built-in assertions

A batch of smaller improvements to the assertions that came with 6.0:

- **`Should-BeString -NormalizeLineEnding`** — compares strings regardless of `CRLF` versus `LF`. If you've ever had a test pass on Windows and fail on a Linux build agent because of line endings, this is for you. (The release candidate called it `-NormalizeNewline`; the final name is `-NormalizeLineEnding`.)
- **`Should-ContainCollection -IgnoreOrder`** — checks that a collection contains certain items, in any order.
- **Wildcard hints on `Should-Throw -ExceptionMessage`** — `-ExceptionMessage` matches with `-like`, so square brackets in the message are wildcard characters. Pester now tells you when that's why a match failed.
- **A consistency pass** — positions and mandatory parameters were aligned across the assertions. That's the right move for a young API, but if you call an assertion with positional parameters, check that it still binds the way you expect. Dead code for a `Should-BeEquivalent -StrictOrder` parameter, which was never usable, was removed.

```powershell
Describe 'New in 6.1' -Tag 'Assertions' {
    It 'ignores line endings' {
        "a`r`nb" | Should-BeString "a`nb" -NormalizeLineEnding
    }

    It 'ignores order in a sub-collection' {
        1, 2, 3 | Should-ContainCollection @(3, 1) -IgnoreOrder
    }

    It 'explains wildcard characters in an exception message (fails on purpose)' {
        { throw 'value is [1]' } | Should-Throw -ExceptionMessage 'value is [1]'
    }
}

Describe 'Slow things' -Tag 'Slow', 'Integration' {
    It 'is tagged' {
        $true | Should-BeTrue
    }
}
```

The new `Output.ShowTags` option shows the tags in the output, which helps when you're working out why a tag filter included or skipped something:

```powershell
pwsh -NoProfile -File ./run.ps1 -Version 6.1.0 -Path ./v6.1/Assertions.Tests.ps1 -ShowTags
```

```text
Pester v6.1.0

Running tests from 1 files.

Running tests from 'C:\code\pester-v6-demo\v6.1\Assertions.Tests.ps1'
Describing New in 6.1 [Tags: Assertions]
  [+] ignores line endings 184ms
  [+] ignores order in a sub-collection 39ms
  [-] explains wildcard characters in an exception message (fails on purpose) 186ms
   Expected an exception, with message like 'value is [1]' to be thrown, but the message was 'value is [1]'.
   Hint: -ExceptionMessage matches using wildcards (-like). The messages are identical except for the wildcard characters [ ] * ? in -ExceptionMessage. Escape them with a backtick (`[) or use [System.Management.Automation.WildcardPattern]::Escape() to match them literally.
   at { throw 'value is [1]' } | Should-Throw -ExceptionMessage 'value is [1]', C:\code\pester-v6-demo\v6.1\Assertions.Tests.ps1:11

Describing Slow things [Tags: Slow, Integration]
  [+] is tagged 67ms
Tests completed in 1.74s
Tests Passed: 3, Failed: 1, Skipped: 0, Inconclusive: 0, NotRun: 0
```

Also small but nice: skipped data-driven tests now show their real names (`handles foo`, `handles bar`) instead of the raw template (`handles <_>`) for every case.

## Experimental: global mocks

This is the one the maintainers explicitly want feedback on. Normally, to mock a command that's called *inside* a module, you need `-ModuleName` on `Mock` so the mock lands in the module's scope. With `Mock.Global = $true`, a mock applies everywhere — your test, the module under test, and any other module that calls the command.

The team wants this to become the default in Pester 7, and the release notes put it plainly: *"A report that says 'turned it on, nothing changed' is just as useful to us."*

To see it, a tiny module with a function that calls `Get-Date`:

```powershell
function Get-Today {
    (Get-Date).ToString('yyyy-MM-dd')
}

function Get-Year2000 {
    (Get-Date -Year 2000).Year
}
```

And a test that mocks `Get-Date` without `-ModuleName`:

```powershell
BeforeAll {
    Import-Module $PSScriptRoot/DemoModule.psm1 -Force
}

AfterAll {
    Remove-Module DemoModule -Force
}

Describe 'Mocking a command used inside a module' {
    It 'sees the mock from inside the module' {
        Mock Get-Date { [datetime]'2026-08-16' }
        $null = Get-Today
        Should-Invoke Get-Date -Times 1 -Exactly
    }

    It 'falls through or fails when the parameter filter does not match' {
        Mock Get-Date { [datetime]'1999-12-31' } -ParameterFilter { $Year -eq 1999 }
        Get-Year2000 | Should-Be 2000
    }
}
```

Run it without and with global mocks:

```powershell
pwsh -NoProfile -File ./run.ps1 -Version 6.1.0 -Path ./v6.1/GlobalMock.Tests.ps1
pwsh -NoProfile -File ./run.ps1 -Version 6.1.0 -Path ./v6.1/GlobalMock.Tests.ps1 -GlobalMock
```

```text
Pester v6.1.0

Running tests from 1 files.

Running tests from 'C:\code\pester-v6-demo\v6.1\GlobalMock.Tests.ps1'
Describing Mocking a command used inside a module
  [-] sees the mock from inside the module 493ms
   Expected Get-Date to be called 1 time exactly, but was called 0 times
   Performed invocations:
     <none>
   at Should-Invoke Get-Date -Times 1 -Exactly, C:\code\pester-v6-demo\v6.1\GlobalMock.Tests.ps1:14
  [+] falls through or fails when the parameter filter does not match 50ms
Tests completed in 1.66s
Tests Passed: 1, Failed: 1, Skipped: 0, Inconclusive: 0, NotRun: 0
Pester v6.1.0

Running tests from 1 files.

Running tests from 'C:\code\pester-v6-demo\v6.1\GlobalMock.Tests.ps1'
Describing Mocking a command used inside a module
  [+] sees the mock from inside the module 439ms
  [-] falls through or fails when the parameter filter does not match 64ms
   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 1999 }  bound parameters: Year = 2000
Tests completed in 1.31s
Tests Passed: 1, Failed: 1, Skipped: 0, Inconclusive: 0, NotRun: 0
```

The second test is there because the release notes contradict themselves a little. Pester 6.0 removed mock fall-through — when no parameter filter matches, you get an error instead of the real command. But the 6.1 notes, describing a global mock with a `-ParameterFilter`, say that *"the rest fall through to the real command."*

So I tried it. Without global mocks, the first test fails — the mock in the test doesn't reach into `DemoModule`, so `Get-Date` was called 0 times — and the second test passes because the real `Get-Date` runs. With `Mock.Global` on, the first test passes, and the second one *fails* with the same "No mock for command" error you get in 6.0. So in 6.1.0, unmatched calls to a global mock don't fall through to the real command either. If you need some calls to behave normally, add a default mock without a filter that does what you need.

## Experimental: shuffled test order

`Run.Shuffle = $true` runs your tests in a random order, which is a good way to find tests that only pass because an earlier test left something behind. The seed is printed at the start of every shuffled run, so you can repeat that exact order with `Run.ShuffleSeed`, and `#pester:no-shuffle` at the top of a file keeps it in order.

```powershell
$config = New-PesterConfiguration
$config.Run.Shuffle = $true
```

Parallel runs (still experimental) also improved: code coverage is now collected across the parallel workers instead of falling back to a sequential run.

## The option that disappeared

This one isn't in the 6.1.0 release notes, so it's easy to miss. Pester 6.0 had a `Run.BeforeContainer` configuration option, for setup code that runs before each test file:

```powershell
$config.Run.BeforeContainer = { . './setup.ps1' }
```

It was [removed during the 6.1 alphas](https://github.com/pester/Pester/pull/2859), because relative paths in it behaved inconsistently. The file-based convention stays: a `Pester.BeforeContainer.ps1` file in the root of your repository. If you used the option, move its contents into that file. (And in [6.2](/2026-09-14-pester-v6-2/) that convention grew into something more useful.)

To see what happens when the old option is still set:

```powershell
pwsh -NoProfile -File ./v6.1/legacy-beforecontainer.ps1 -Version 6.0.0
pwsh -NoProfile -File ./v6.1/legacy-beforecontainer.ps1 -Version 6.1.0
```

```text
Pester 6.0.0

Running tests from 1 files.
setup ran
[+] C:\code\pester-v6-demo\v6.1\Plain.Tests.ps1 1.04s
Tests completed in 1.08s
Tests Passed: 1, Failed: 0, Skipped: 0, Inconclusive: 0, NotRun: 0
Pester 6.1.0
InvalidOperation: C:\code\pester-v6-demo\v6.1\legacy-beforecontainer.ps1:7
Line |
   7 |  $config.Run.BeforeContainer = { Write-Host 'setup ran' }
     |  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | The property 'BeforeContainer' cannot be found on this object. Verify that the property exists and can be set.

Running tests from 1 files.
[+] C:\code\pester-v6-demo\v6.1\Plain.Tests.ps1 752ms (1 test)
Tests completed in 774ms
Tests Passed: 1, Failed: 0, Skipped: 0, Inconclusive: 0, NotRun: 0
```

On 6.0 the setup runs. On 6.1, setting the option throws `The property 'BeforeContainer' cannot be found on this object` — but that's a non-terminating error, so the script carries on and Pester runs the tests anyway, *without* your setup. In a pipeline that doesn't use `$ErrorActionPreference = 'Stop'`, that red line may be the only hint that something is missing.

## Known issues

One thing worth knowing before you update:

**Unknown configuration keys are silently ignored** ([#2975](https://github.com/pester/Pester/issues/2975)), so a typo like `Run.Paralel` simply does nothing.

> **Update (September 2026):** after this post, [#3013](https://github.com/pester/Pester/issues/3013) was reported: 6.0 and 6.1 fail to import on early PowerShell 7.4 patch releases with `Cannot convert "PesterConfigurationDeserializer" from String to Type`. Both that and the configuration key issue are fixed in [6.2](/2026-09-14-pester-v6-2/).

On the v5 side, [5.9.1](https://github.com/pester/Pester/releases/tag/5.9.1) was released the same day with a backported mocking fix — the maintenance-mode promise in action.

## Wrap

`New-ShouldAssertion` is the reason to pick up 6.1 if you write a lot of tests against your own objects; the rest is polish on a young set of assertions. And if you've got a spare moment, turn on `Mock.Global` on one of your suites and tell the maintainers what happened — they asked.

Happy testing 😊

