Skip to content
6 min read

Tagging and Filtering Which Tests Run

  • powershell
  • pester
  • testing
  • tags
  • filtering
  • configuration

A growing suite is a good problem, right up until it takes two minutes to run and you stop running it. The fix isn't fewer tests—it's the ability to run a subset: just the fast unit tests during a tight edit loop, the whole pile (slow integration tests included) before you push. Pester gives every test a set of tags, and lets you filter on them so "run only the fast ones" becomes one setting.

What you'll learn

  • How to add -Tag to Describe, Context, and It
  • Filtering which tests run with the configuration object's Filter.Tag
  • Excluding tests with Filter.ExcludeTag
  • Common tag conventions like Unit, Integration, and Slow
  • Filtering by file path as a complementary tool

Tagging your tests

Any Describe, Context, or It can take a -Tag parameter with one or more labels. A tag on a Describe flows down to everything inside it, so you usually tag at the highest level that makes sense and let the children inherit.

# Math.Tests.ps1
BeforeAll {
    function Add-Numbers { param($A, $B) $A + $B }
}

Describe 'Add-Numbers' -Tag 'Unit' {
    It 'adds two positives' {
        Add-Numbers 2 3 | Should -Be 5
    }
}

Describe 'Database integration' -Tag 'Integration', 'Slow' {
    It 'connects to the real database' {
        # imagine a real connection here
        $true | Should -BeTrue
    }
}

Here the unit test carries the Unit tag, and the integration block carries two tags, Integration and Slow. A test can have as many tags as you like; filtering matches if any of its tags match.

You can also tag a single It when only one test in a block deserves a label:

Describe 'Get-Report' -Tag 'Unit' {
    It 'formats the header' {
        'ok' | Should -Be 'ok'
    }

    It 'renders the full PDF' -Tag 'Slow' {
        # this one is heavy
        $true | Should -BeTrue
    }
}

That It is now tagged with both Unit (inherited) and Slow (its own).

Filtering with the configuration object

In Pester 5 you choose which tagged tests run through the configuration object, not a command-line -Tag switch. Build a configuration with New-PesterConfiguration, point it at your tests, set Filter.Tag, and pass it to Invoke-Pester:

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

Invoke-Pester -Configuration $config

That run executes only tests tagged Unit — every Integration and Slow test is skipped. Filter.Tag accepts an array, and the match is an OR: list several tags and a test runs if it carries any one of them.

$config = New-PesterConfiguration
$config.Run.Path = './Tests'
$config.Filter.Tag = 'Unit', 'Smoke'   # run tests tagged Unit OR Smoke

Invoke-Pester -Configuration $config

Coming from Pester v4? You may remember Invoke-Pester -Tag 'Unit' on the command line. In v5 the supported, future-proof way is the configuration object's Filter.Tag. Reach for the config first.

Excluding tests with Filter.ExcludeTag

The mirror image of "run only these" is "run everything except these." That is Filter.ExcludeTag. It is the everyday move for keeping slow tests out of your fast inner loop:

$config = New-PesterConfiguration
$config.Run.Path = './Tests'
$config.Filter.ExcludeTag = 'Slow', 'Integration'

Invoke-Pester -Configuration $config

Now everything runs except tests tagged Slow or Integration — exactly what you want during rapid edit-test cycles.

You can combine the two. Filter.Tag selects the candidate set, then Filter.ExcludeTag removes from it. "Run the unit tests, but skip the slow ones," reads like this:

$config = New-PesterConfiguration
$config.Run.Path = './Tests'
$config.Filter.Tag = 'Unit'
$config.Filter.ExcludeTag = 'Slow'

Invoke-Pester -Configuration $config

Exclusion wins when a test matches both, so a test tagged Unit, Slow is left out by the run above.

Tag conventions worth adopting

Tags are free-form strings, which means consistency is on you. A small, agreed vocabulary goes a long way:

  • Unit — fast, isolated, no external dependencies. The default for everyday runs.
  • Integration — touches a real database, file share, or network. Slower, run less often.
  • Slow — anything that takes noticeable time, regardless of type.
  • Smoke — a tiny critical subset you run to confirm "is it broadly alive?"

Pick names as a team, write them down, and use them everywhere. The value of a tag is only as good as everyone spelling it the same way.

Filtering by path and line, too

Tags are not the only filter. When you just want to run one file or one folder, Run.Path already narrows things — point it at a single .Tests.ps1:

$config = New-PesterConfiguration
$config.Run.Path = './Tests/Unit/Get-Report.Tests.ps1'

Invoke-Pester -Configuration $config

You can also filter to specific lines with Filter.Line (handy from an editor that knows your cursor position), and to a literal block name with Filter.FullName. Tags are the durable, intent-based filter; path and line are the quick "just this bit right now" filters. Most teams lean on tags for the named subsets and use path filtering ad hoc.

Try it yourself

Take a test file that has both quick checks and a slow or external one. Tag the slow/external Describe with -Tag 'Integration'. Then build a configuration that sets Filter.ExcludeTag = 'Integration' and run it — confirm the summary shows the integration test was not run (Pester reports it as skipped/filtered). Now flip to Filter.Tag = 'Integration' and confirm only that one runs.

Common mistakes

Tag typos that silently match nothing. Filter.Tag = 'Uint' matches no tests and Pester does not warn you — it just runs zero. After a tag run, glance at the count: if it is suspiciously low or zero, check the spelling on both the test and the filter.

Over-tagging. Slapping five tags on every test makes the vocabulary meaningless and filtering unpredictable. Tag for the distinctions you actually filter on, and let inheritance from Describe do most of the work.

Assuming the v4 command-line -Tag is the v5 way. In Pester 5, set Filter.Tag / Filter.ExcludeTag on the configuration object and pass it via -Configuration. Mixing legacy parameters with the config object leads to surprises.

Recap

Tags label your tests by intent — -Tag on Describe, Context, or It, with Describe tags flowing down to children. To run a subset, set Filter.Tag on a New-PesterConfiguration object; to skip a subset, set Filter.ExcludeTag; combine them for "these but not those," where exclusion wins ties. Lean on a small, consistent tag vocabulary, and use Run.Path for quick one-file runs. With your suite organized and filterable, you are ready to start isolating code from its dependencies — which is what mocking is for.

Next up: Part 17 — What Mocking Is and When You Actually Need It.