Skip to content
6 min read

Configuring Pester with New-PesterConfiguration

  • PowerShell
  • Pester
  • Testing
  • Configuration
  • Beginners
  • Automation

Passing a flag or two to Invoke-Pester is fine. Passing ten of them, every time, from memory, isn't. Once you want detailed output and a tag filter and coverage and a results file, the command line turns into a wall of parameters you copy-paste and inevitably get wrong. Pester 5 has a better answer: a single configuration object you build once, set piece by piece, save in a script, and reuse everywhere from your laptop to CI. This post is the one that makes every later automation step click into place.

What you'll learn

  • How to create a configuration object with New-PesterConfiguration
  • The key sections: Run, Output, Filter, CodeCoverage, and TestResult
  • How to run with Invoke-Pester -Configuration $cfg
  • How to store a config in a script for repeatable runs
  • Why Pester 5 favors this object over splatted parameters

Start with the object

New-PesterConfiguration hands you a fully-populated configuration object with every setting already at its default. You then tweak only what you care about.

$config = New-PesterConfiguration
$config

Printing it shows the sections: Run, Filter, CodeCoverage, TestResult, Should, Debug, and Output. Each section holds typed properties. Because defaults are already in place, you never start from a blank slate, you start from "sensible" and adjust.

Run: what to execute

The Run section controls what Pester runs and what it gives back.

$config = New-PesterConfiguration
$config.Run.Path = './Tests'        # file or folder to discover
$config.Run.PassThru = $true        # return a result object

$result = Invoke-Pester -Configuration $config
$result.Result                      # 'Passed' or 'Failed'

Run.Path accepts a single path or an array of paths, so you can target one file while developing and a whole folder in CI. Run.PassThru returns the rich result object, which you will lean on for thresholds and reporting.

Output: how much you see

Output.Verbosity decides how chatty the run is. The useful values are None, Normal, Detailed, and Diagnostic.

$config = New-PesterConfiguration
$config.Run.Path = './Tests'
$config.Output.Verbosity = 'Detailed'

Invoke-Pester -Configuration $config

Detailed prints every Describe, Context, and It with indentation that mirrors your structure, which is exactly what you want while developing. In CI you often drop to Normal to keep logs short, and reach for Diagnostic only when you are debugging discovery itself.

Filter: which tests run

If you tagged tests back in Part 16, the Filter section is how you act on those tags through configuration. This is the v5 way; the v4 habit of passing -Tag on the command line does not map cleanly onto the configuration object.

$config = New-PesterConfiguration
$config.Run.Path = './Tests'
$config.Filter.Tag = 'Unit'            # run only Unit-tagged tests
$config.Filter.ExcludeTag = 'Slow'     # but never the Slow ones

Invoke-Pester -Configuration $config

Filter.Tag and Filter.ExcludeTag both accept arrays, and ExcludeTag wins when a test matches both. You can also filter by Filter.FullName (the Describe/It text) or Filter.Line for surgical, single-test runs.

CodeCoverage and TestResult: the artifacts

These two sections produce the files automation cares about. Coverage we met in Part 22; TestResult writes a machine-readable summary of pass/fail that CI dashboards can publish.

$config = New-PesterConfiguration
$config.Run.Path = './Tests'

$config.CodeCoverage.Enabled = $true
$config.CodeCoverage.Path = './src'
$config.CodeCoverage.OutputPath = './coverage.xml'

$config.TestResult.Enabled = $true
$config.TestResult.OutputFormat = 'NUnitXml'
$config.TestResult.OutputPath = './testResults.xml'

Invoke-Pester -Configuration $config

After this run you have coverage.xml (JaCoCo) and testResults.xml (NUnit), the two artifacts the CI post (Part 24) publishes.

Putting it together in a script

The real payoff is saving the whole thing in a script, say run-tests.ps1, so every run is identical and lives in version control. No more remembering flags.

# run-tests.ps1 — the one true way to run this repo's tests
$config = New-PesterConfiguration

$config.Run.Path        = "$PSScriptRoot/Tests"
$config.Run.PassThru    = $true
$config.Output.Verbosity = 'Detailed'

$config.CodeCoverage.Enabled    = $true
$config.CodeCoverage.Path       = "$PSScriptRoot/src"
$config.CodeCoverage.OutputPath = "$PSScriptRoot/coverage.xml"

$config.TestResult.Enabled      = $true
$config.TestResult.OutputFormat = 'NUnitXml'
$config.TestResult.OutputPath   = "$PSScriptRoot/testResults.xml"

$result = Invoke-Pester -Configuration $config

if ($result.FailedCount -gt 0) {
    throw "$($result.FailedCount) test(s) failed."
}

Now anyone, including your CI runner, gets the exact same behavior by typing ./run-tests.ps1. Using $PSScriptRoot keeps the paths correct no matter where the script is invoked from.

Why the object beats splatted parameters

You can still splat a hashtable of legacy parameters at Invoke-Pester, but in v5 that path is deprecated and, worse, you cannot mix it with -Configuration. The object wins for concrete reasons: it is strongly typed (so a typo in a section name errors instead of silently doing nothing), it is discoverable (print it and read every option), it is reusable across machines, and it is version-controllable. One object, one source of truth, zero "what flags did we use last time?"

Try it yourself

Take a Pester command line you actually run, flags and all, and reproduce it as a single configuration object in a run-tests.ps1 script. Set each flag's equivalent property, add PassThru, and throw on FailedCount. Run the script and confirm you get the same result as your old command, then delete the old command from your notes. You now have a repeatable, shareable test run.

Common mistakes

Mixing legacy parameters with -Configuration. Pester 5 will not let you combine the old splatted parameter set with the configuration object. Pick the object and commit to it.

Not knowing the defaults. Every section already has a default value. Print New-PesterConfiguration and read it once so you only override what truly needs changing, rather than re-stating defaults.

Editing config after invoking. Set every property before you call Invoke-Pester. Changing the object afterward does nothing to a run that has already finished, set it, then invoke.

Recap

New-PesterConfiguration gives you one typed object that replaces a fistful of command-line flags. Set Run.Path for what to execute, Output.Verbosity for how much to see, Filter.Tag/ExcludeTag for which tests, and CodeCoverage/TestResult for the artifacts automation needs, then run it with Invoke-Pester -Configuration $cfg. Save the whole thing in a script with $PSScriptRoot-based paths and you have a repeatable, version-controlled run that behaves identically everywhere. Set it before you invoke, and never mix it with the legacy parameters.

Next up: Part 24 — Running Pester in CI/CD (GitHub Actions & Azure Pipelines), where we take this exact configuration object and wire it into a pipeline that runs your tests on every push and fails the build on red.