Instruction file imported from fadwen/Powershell-Copilot-Standards (
.github/instructions/pester.instructions.md). Copyright stays with the author.
PowerShell Pester Testing - Core Instructions
Generate enterprise-grade Pester test suites following these core requirements.
Target version: Pester 6.2+ on Windows PowerShell 5.1 or PowerShell 7.4+. Pester 6 removed
support for PowerShell 3, 4, 6, and unsupported 7.x. 6.1 and 6.2 are additive for test files - no
test file needs to change to move between 6.0, 6.1, and 6.2. The one thing 6.2 changes underneath a
suite is Pester.BeforeContainer.ps1: top-level code in it now runs at discovery only, so run-time
setup there must sit in a BeforeAll - see
Moving From 6.1 to 6.2.
NOTE: Do not use Unicode emojis in any generated code, documentation, or test output. Use plain text descriptions and standard ASCII characters only.
Quick Reference
Test Structure Requirements
- Directory Structure: Follow Test Structure Guide
- Coverage Target: Minimum 80% code coverage for production code
- Test Types: Unit (mandatory), Integration, Performance, Security
- Quality Gates: All tests pass, performance within limits, security validation
- File Isolation: Every test file must be self-contained - see below
Core Test Patterns
Generate tests using these templates:
- Unit Tests: Use Unit Test Template
- Integration Tests: Use Integration Test Template
- Performance Tests: Use Performance Test Template
- Security Tests: Use Security Test Template
Assertions
- Guide: Use Assertion Guide
- New test files: prefer the Pester 6
Should-*assertions (dash, no space) - they are type-aware and produce far better failure messages. - Existing files: keep the file's existing style. Do not mix
Should -BeandShould-Bewithin a single file. - Do not set
Should.DisableV5 = $trueuntil every test file in the repository is migrated. - Custom assertions: the
Should-*set is open for extension in 6.1 viaNew-ShouldAssertion- see Custom Assertion Guide. Write one only when the same domain rule is asserted across several files and naming the offending value is the point.
Migrating From Pester 5
Follow the Pester 6 Migration Guide. The three changes that break existing suites outright:
Assert-MockCalledandAssert-VerifiableMockwere removed - useShould -Invoke/Should -InvokeVerifiable(orShould-Invoke/Should-NotInvoke).-FocusandSet-ItResult -Pendingwere removed.- A
-ForEach/-TestCasesthat evaluates to$nullor@()now fails discovery.
A second BeforeAll (or BeforeEach, AfterAll, AfterEach) in one block threw in 6.0 and 6.1.
6.2 allows it again, so it is no longer a migration item.
Test Requirements Checklist
- File Isolation: File imports its own modules and does its own discovery-time setup
- Parameter Validation: Test all input validation scenarios
- Error Handling: Verify graceful error handling with meaningful messages
- Mocking: Mock all external dependencies appropriately
- Performance: Include timing and memory usage validation
- Security: Test input sanitization and credential handling
- Pipeline Support: Verify pipeline input/output functionality
- Non-Empty Test Cases: No
-ForEach/-TestCasesexpression can yield$nullor@()
Configuration & Execution
- Configuration: Use Pester Configuration Guide
- Test Execution: Use Test Execution Guide
- CI/CD Integration: Follow CI/CD Integration Guide
Experimental Options
Three configuration options are experimental, off by default, and may change. Do not enable them in generated code unless asked; when a project does enable one, these are the consequences:
| Option | Effect | Note |
|---|---|---|
Run.Parallel |
One test file per runspace | Requires file-based containers. Runs on PowerShell 7 and, from 6.2, on Windows PowerShell 5.1. Coverage works from 6.1 but is forced onto slower breakpoint mode |
Run.Shuffle |
Randomizes file, block, and test order | Fails tests that depend on declaration order. Prints a seed; Run.ShuffleSeed replays it. Opt a file out with #pester:no-shuffle |
Mock.Global |
A mock applies to calls from any module in the runspace | -ModuleName becomes a resolution hint, not a scope. Does not reinstate fall-through - a -ParameterFilter guard still needs a default mock |
All three are per-run configuration, not per-file, apart from the #pester:no-parallel and
#pester:no-shuffle directives.
Quality Standards
- Execution Speed: Unit tests <30s total, integration tests <5min
- Test Isolation: Tests must be independent and repeatable
- Documentation: Include troubleshooting references in
./Troubleshooting/folder - Enterprise Standards: Follow PowerShell community best practices
Implementation Requirements
When generating Pester tests:
- Analyze Function: Determine test types needed (Unit/Integration/Performance/Security)
- Apply Templates: Use appropriate templates from supporting documentation
- Mock Dependencies: Mock all external calls using the Mocking Patterns Guide
- Validate Coverage: Ensure 80%+ code coverage with meaningful assertions
- Test Data: Use Test Data Management Guide
- Integration: Configure for CI/CD pipeline execution
Every Test File Must Be Self-Contained
Pester 6 discovers and runs one file at a time, interleaving discovery and execution, rather
than discovering every file up front. Discovery-time side effects therefore do not carry across
files, and under Run.Parallel each file is discovered in its own runspace.
Each test file must import the modules it needs and perform its own discovery-time setup:
#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '6.2.0' }
BeforeDiscovery {
# Anything needed to BUILD the test tree (-ForEach data, helper commands)
Import-Module "$PSScriptRoot/../../ModuleName.psd1" -Force
}
BeforeAll {
# Anything needed to RUN the tests
Import-Module "$PSScriptRoot/../../ModuleName.psd1" -Force
}
When several files share bootstrap, put it in a Pester.BeforeContainer.ps1 rather than relying on
another file having run first. From 6.2 every such file from Run.RepoRoot down to the test file's
own folder applies, outermost first, so unit and integration tests can each carry their own setup.
The file follows the same rule as a test file: top-level code runs at discovery only, and
anything the tests need at run time goes in a BeforeAll:
# Pester.BeforeContainer.ps1 - at the repository root, or in any folder under it
BeforeAll {
Import-Module "$PSScriptRoot/Source/ModuleName.psd1" -Force
. "$PSScriptRoot/Tests/TestHelpers/TestHelpers.ps1"
}
The Run.BeforeContainer option that also did this was removed in 6.1; the convention file is
the only mechanism. The chain starts at Run.RepoRoot - see
Pester Configuration Guide.
Quick Test Generation Pattern
# Standard test structure for any function
Describe "Function-Name" -Tag "Unit", "Public" {
Context "Parameter Validation" { <# Validation tests #> }
Context "Core Functionality" { <# Main logic tests #> }
Context "Error Handling" { <# Error scenarios #> }
Context "Performance Requirements" { <# Performance tests #> }
}
From 6.2 a block may hold more than one BeforeAll, BeforeEach, AfterAll, or AfterEach.
Setups run in declaration order and teardowns in reverse, so group setup by what it sets up rather
than merging unrelated work into one block. 6.0 and 6.1 throw on the second one.
Tagging
Tag every Describe block. None is a reserved filter value in Pester 6 meaning "tests with no
tags" - never use it as a literal tag. Verify tagging coverage with:
Invoke-Pester -Path ./Tests -TagFilter 'None' # a well-tagged suite RUNS zero tests
Read the Passed count, not the discovered count - TotalCount ignores the filter. Scripted
gates must count $result.Tests | Where-Object ShouldRun; see
Test Structure Guide.
Testing Private Functions
A module built to these standards exports only what FunctionsToExport names, so private functions
are unreachable from a test file. Use InModuleScope to run assertions inside the module's scope,
where internal functions and classes are visible:
It 'Returns a session for the target environment' {
InModuleScope MyModule {
$session = Connect-Service -Environment 'Test' -CorrelationId ([guid]::NewGuid())
$session.Environment | Should-Be 'Test'
}
}
InModuleScope is also how you mock a private function that a public one calls, and how you reach a
class defined in Classes/:
InModuleScope MyModule {
Mock Get-ServiceStatus { throw 'unreachable' }
'A', 'B' | Get-Data -ErrorAction SilentlyContinue | Should-BeFalsy
}
Do not export a function purely to make it testable - that widens the public contract to serve the tests. Reach into the module instead.
Worked Examples
These are complete, passing implementations of the patterns above. Prefer matching them over inventing a structure:
- Module-Structure-Example/Tests -
module contract, class assertions,
InModuleScopefor two private functions, and a per-item failure path - Testing-Examples/Basic-Function.Tests.ps1 -
CIM mocking,
-RemoveParameterType,-TestCases, and mock scoping across contexts - Tools/Tests - tests for the repository's own tooling, including fixture files written per test and error-path coverage
Documentation Integration
All test implementations must reference:
- Troubleshooting: Link to
./Troubleshooting/folder documentation - Supporting Guides: Reference detailed templates and patterns
- Enterprise Standards: Follow established PowerShell development practices
For detailed implementation guidance, templates, and examples, refer to the supporting documentation
files in the pester-supporting-docs/ folder.