Skip to main content

Choosing what runs

Running everything is the right default. It stops being practical the moment part of your suite is slow, or platform-specific, or only meaningful against a real database.

Pester gives you different tools for that, and the difference matters: filters decide what gets selected to run, skip marks a test as deliberately not run.

Tagging tests

The most used filter in Pester is tags. -Tag goes on Describe, Context or It, and is inherited by everything inside. Your two private-function files are a natural group — they reach into the module with InModuleScope, and they are the tests you would drop first if you only wanted to check the public surface:

Planetarium/Private/ConvertTo-AstronomicalUnit.Tests.ps1
Describe 'ConvertTo-AstronomicalUnit' {
Describe 'ConvertTo-AstronomicalUnit' -Tag 'Internal' {
Planetarium/Private/Test-PlanetName.Tests.ps1
Describe 'Test-PlanetName' {
Describe 'Test-PlanetName' -Tag 'Internal' {

Now you can run the public surface on its own:

Invoke-Pester -Path ./Planetarium -ExcludeTagFilter 'Internal'
Tests Passed: 8, Failed: 0, Skipped: 0, Inconclusive: 0, NotRun: 7

Or only the internals:

Invoke-Pester -Path ./Planetarium -TagFilter 'Internal'
Tests Passed: 7, Failed: 0, Skipped: 0, Inconclusive: 0, NotRun: 8

The same two filters exist on the configuration object as Filter.Tag and Filter.ExcludeTag, which is how you would set them in test.ps1 or from a CI variable.

NotRun is not Skipped

Read those totals again: the filtered-out tests are counted as NotRun, and Skipped stays at zero.

Pester discovers every test either way — that is how it knows there are fifteen — then runs only the ones your filter selected. NotRun means "not selected". Skipped means "selected, then deliberately passed over", which is the next section. Keeping them in separate columns means a filtered run cannot quietly hide a test you thought was executing.

Typical tags used in projects are Slow, Integration, Unit, WindowsOnly etc. The useful test is whether you would ever want to run with the tag, or without it — a tag nobody filters on is just a comment.

Skipping tests

-Skip marks a test as not to be run, while keeping it visible in the output:

It 'Talks to the real API' -Skip {
# not written yet
}
[!] Talks to the real API 1ms

[!] rather than [+] or [-], and the summary counts it under Skipped. That visibility is the entire point: a skipped test nags, whereas a commented-out test is invisible and eventually forgotten.

It becomes very useful when combined with a condition:

It 'Uses the Windows registry' -Skip:(-not $IsWindows) {
# ...
}

On Windows this runs; everywhere else it reports as skipped instead of failing. That is how a cross-platform suite handles the parts that genuinely cannot run everywhere — and it is how you would keep the CI matrix in the last module green if the module ever grew a platform-specific feature.

Both -Skip examples above are illustrations rather than tests to add — Planetarium has nothing that needs skipping yet.

BeforeDiscovery

Here is the catch, and it is the one thing on this page that trips people up.

-Skip:(-not $IsWindows) is evaluated when Pester discovers your tests, not when it runs them. Same for the -ForEach list you wrote back in the private-functions page. So a variable set in BeforeAll is no good to either of them — BeforeAll has not run yet.

BeforeDiscovery is the block that runs during discovery, and it exists precisely for building the data those parameters need. Let's try it out:

Planetarium/Private/Test-PlanetName.Tests.ps1
BeforeDiscovery {
$KnownPlanets = @('Mercury', 'Earth', 'Neptune')
}

BeforeAll {
Import-Module "$PSScriptRoot/../Planetarium.psd1" -Force
}

Describe 'Test-PlanetName' -Tag 'Internal' {
It 'Accepts <_>' -ForEach @('Mercury', 'Earth', 'Neptune') {
It 'Accepts <_>' -ForEach $KnownPlanets {
InModuleScope Planetarium -Parameters @{ Name = $_ } {
Test-PlanetName -Name $Name | Should-BeTrue
}
}

It 'Rejects Pluto' {
InModuleScope Planetarium {
Test-PlanetName -Name 'Pluto' | Should-BeFalse
}
}
}
./test.ps1
Describing Test-PlanetName
[+] Accepts Mercury 16ms
[+] Accepts Earth 2ms
[+] Accepts Neptune 2ms
[+] Rejects Pluto 2ms

Identical results — three separately named tests, same as before. What changed is that the list now has a name and a home, so it can grow, be read from a file, or be shared between several It blocks.

That is the everyday use of BeforeDiscovery: generating tests from data. One test per file in a folder, one per row of a fixture, one per supported culture.

A variable from BeforeDiscovery is not available inside your tests

BeforeDiscovery runs in the discovery pass and It bodies run in the run pass, and variables do not survive the trip. $KnownPlanets is empty if you reference it inside an It.

The way across is the data itself. -ForEach hands each item to the test as $_, and when you pass hashtables their keys arrive as named variables — available in BeforeAll too, not just in It:

Context 'Earth' -ForEach @(@{ Planet = 'Earth'; Order = 3 }) {
BeforeAll { $expected = "$Planet is number $Order" }
It 'knows its order' { $expected | Should-Be 'Earth is number 3' }
}

If you want the full picture of which code runs in which pass, Discovery and run is the page to read — it is the model that explains most surprising Pester behaviour.

Before you move on

0/6

Next we'll be testing code without its dependency.