Skip to content

6.1.0-rc1

Pre-release
Pre-release

Choose a tag to compare

@nohwnd nohwnd released this 06 Aug 20:37
ec8ca45

Pester 6.1.0-rc1

馃檵 Want to share feedback or report a bug? Open an issue
or start a discussion.

Pester 6.1.0 builds on the 6.0.0 release. The headline is that the new Should-* assertions are now
open for extension: you can write your own typed assertion with New-ShouldAssertion and it behaves
exactly like a built-in one. Alongside that, this release adds two experimental features worth trying,
global mocks and shuffled test order, and a large round of assertion, output, and mocking fixes.

Pester 6 runs on Windows PowerShell 5.1 and PowerShell 7.4+.

This is a release candidate. The API is what we intend to ship as 6.1.0, but the features marked
experimental may still change based on your feedback.

What's new?

Write your own Should-* assertions with New-ShouldAssertion

The Should-* assertions in 6.0.0 were a closed set. Now you can author your own and it gets the same
building blocks a built-in assertion has: pipeline input collection, consistent value formatting, the
diagnostic hint when someone pipes a collection into a value assertion, and the shared failure path
that makes soft assertions and -ParameterFilter work.

You call New-ShouldAssertion once at the top of your function, then use the object it returns. A
passing result is implicit, you only call Fail() when the check does not hold, and the message
supports <expected>, <actual>, <because> and your own <key> tokens:

function Should-BeAwesome {
    [CmdletBinding()]
    param (
        [Parameter(Position = 1, ValueFromPipeline)] $Actual,
        [Parameter(Position = 0)]                    $Expected = 'Awesome',
        [string] $Because
    )

    $assert = New-ShouldAssertion -Caller $PSCmdlet -Actual $Actual -Buffer $Input
    $Actual = $assert.Actual()

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

And it is used, and fails, just like a real one:

'Awesome' | Should-BeAwesome              # passes
'meh'     | Should-BeAwesome -Because 'the docs promised' 'Awesome'
# Expected 'Awesome', because the docs promised, but got 'meh'.

-As (Scalar by default, or ExactType, Collection, CollectionItems, None) selects how the
piped input is collected and how the input hint is worded, so a collection assertion reads its input
as a collection just like Should-BeCollection does. Your custom assertion also works inside a mock
-ParameterFilter with no extra work.

Sharper assertions

The new assertion family got a round of fixes that make the messages and the parameters behave
consistently:

  • Should-BeString -NormalizeNewline compares strings ignoring the difference between `n and
    `r`n, which is what you want when a file was written on a different platform:

    "a`r`nb" | Should-BeString "a`nb" -NormalizeNewline   # passes
  • Should-BeString points its caret at the first differing character, so a long string diff shows
    you exactly where it went wrong instead of making you count.

  • Should-ContainCollection -IgnoreOrder finds the expected items in any order:

    1, 2, 3 | Should-ContainCollection @(3, 1) -IgnoreOrder   # passes
  • Should-Throw reports the real exception type. When an assertion inside the scriptblock throws,
    the message shows the actual exception type rather than Pester's wrapper.

  • Type assertions honor custom PSTypeNames, so an object you decorated with a synthetic type name
    asserts against that name.

  • Consistency pass: -Actual sits at the same position across the assertions, -Expected is
    mandatory where it always should have been (Should-NotBeString, Should-BeFasterThan,
    Should-BeSlowerThan), Should-Throw -Because is named-only, and -TrimWhitespace is available on
    Should-NotBeString.

  • Formatting a complex object no longer looks like a hang. Values that used to expand into a huge,
    slow tree (a CommandInfo, for example) are now summarised to something short like
    FunctionInfo{Name=Invoke-Pester}.

Show tags in the console output

Output.ShowTags appends the tags of each Describe, Context and It to its output line, which
makes it easy to see what a -Tag / -ExcludeTag filter is actually matching:

$config = New-PesterConfiguration
$config.Output.ShowTags = $true
# Describing Get-Planet [Tags: Slow, Unix]

Skipped data-driven tests get real names

A skipped data-driven test used to show the raw template, Value <_> repeated for every case. Now the
<_> and <key> templates are expanded from the -ForEach data the same way a run test expands them,
so each skipped case has a name you can actually tell apart.

Describe 'd' {
    It 'handles <_>' -Skip -ForEach 'foo', 'bar' { }
}
# [!] handles foo
# [!] handles bar        (was: handles <_> / handles <_>)

Experimental features

These are on by default only when you opt in, and may still change. Try them and tell us what breaks.

Global mocks

A normal mock only applies to calls from the scope where it is defined, or from the module you name
with -ModuleName. To be sure a command like Invoke-WebRequest is never called from any code under
test, you have to know every module that might call it and mock it in each one.

Turn on the experimental Mock.Global option and a mock reaches the command wherever it is called,
from any module or script in the runspace:

$config = New-PesterConfiguration
$config.Mock.Global = $true

You still write the mock exactly as you do today, one mock now covers every caller:

Mock Invoke-WebRequest { '<html />' }
Get-Data                                   # a function in another module that calls Invoke-WebRequest
Should-Invoke Invoke-WebRequest -Times 1

A common use is making sure a command never really runs. Mock it to throw, and combine that with
-ParameterFilter to block only the calls you care about while the rest fall through to the real
command:

# block deleting anything outside TestDrive, from any code under test
Mock Remove-Item { throw 'blocked' } -ParameterFilter { $Path -notlike "$TestDrive*" }

The mock is removed when the test or block that defined it ends, like any other mock, and it is tied
to the run that created it so it cannot leak into a nested Pester-in-Pester run. With the option on,
-ModuleName is only a hint used to resolve the command, not a scope, so your existing mocks keep
working unchanged.

Shuffled test order

Tests that quietly depend on running in a fixed order are a common source of "passes on my machine".
Run.Shuffle reorders your test files, and the blocks and tests inside them, so those hidden
dependencies surface:

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

Items are only reordered within their own level, a test never jumps out of its Context. The run
picks a seed and prints it at the start; set Run.ShuffleSeed to that value to replay the exact same
order:

$config.Run.ShuffleSeed = 1234567890   # repeat a specific shuffle

A single file that genuinely must run in order can opt out with a comment:

#pester:no-shuffle
Describe 'ordered steps' { ... }

Parallel runs keep getting better

The experimental parallel runner from 6.0.0 got several rounds of work in 6.1.0:

  • Code coverage is collected across parallel workers, so turning on parallel no longer means losing
    your coverage numbers.
  • Describing / Context headers render in the parallel Detailed output, so the interleaved
    output is readable instead of a flat list.
  • Worker Write-Verbose / Write-Debug output is replayed interleaved with the tests it came from.
  • A concurrent-import crash in Run.Parallel (a thread-unsafe verb patch) was fixed.

Other improvements and fixes

  • Containers that fail during discovery are now reported in the TestResult XML instead of vanishing.
  • A stray unmatched-label break / continue fails the test instead of aborting the whole run.
  • ExcludePath excludes directories, not just files.
  • Code coverage is collected from Invoke-InNewProcess child processes, and a false negative for
    steppable-pipeline proxy functions was fixed.
  • The JUnit testsuite element gets a timestamp attribute.
  • Mocking fixes: commands with OrderedDictionary parameters on PowerShell 7, cmdlets with no
    DefaultParameterSetName, and friendlier Encoding parameter binding.
  • The mock parameter filter serializer no longer throws when a bound parameter's ToString() throws;
    it fails open and keeps the diagnostic instead of taking down the test.
  • -ExpectedMessage on Should -Throw now points out when the expected and actual message are
    identical except for wildcard characters, so a [bracketed] message no longer fails with two
    identical-looking strings.

Full Changelog: 6.0.0...6.1.0-rc1

Thank you

Thank you to everyone who filed issues, tried the alphas, and sent fixes for this release.

Questions?

Open an issue or start a
discussion.