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
-TagtoDescribe,Context, andIt - Filtering which tests run with the configuration object's
Filter.Tag - Excluding tests with
Filter.ExcludeTag - Common tag conventions like
Unit,Integration, andSlow - 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 $configThat 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 $configComing 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'sFilter.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 $configNow 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 $configExclusion 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 $configYou 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
Describedo most of the work.Assuming the v4 command-line
-Tagis the v5 way. In Pester 5, setFilter.Tag/Filter.ExcludeTagon 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.