This is a real fixture from the PesterForge repo: one mandatory parameter and a try/catch that returns structured objects instead of throwing. The comment-based help is trimmed here for space.
function Read-ConfigFile {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$Path
)
try {
$raw = Get-Content -Path $Path -Raw -Encoding UTF8 -ErrorAction Stop
return [PSCustomObject]@{
Status = 'Succeeded'
Content = $raw
Error = $null
}
} catch [System.IO.FileNotFoundException] {
return [PSCustomObject]@{
Status = 'HandledError'
Content = $null
Error = 'ConfigFileNotFound'
}
} catch [System.UnauthorizedAccessException] {
return [PSCustomObject]@{
Status = 'HandledError'
Content = $null
Error = 'ConfigFileAccessDenied'
}
} catch {
return [PSCustomObject]@{
Status = 'HandledError'
Content = $null
Error = 'UnexpectedReadError'
}
}
}
One command.
PS> New-PesterTests -ScriptPath .\Try-Catch-Function.ps1
This is what comes back. It's real output from PesterForge 2.1.5 against that exact file, trimmed for length: the generator's inline guard comments and three scaffold contexts (Integration, Acceptance, Repository) are cut here, and every cut is marked with a comment.
# =============================================================================
# Script : Read-ConfigFile.Tests.ps1
# Generated by PesterForge
# =============================================================================
# IMPORTANT: This generated test contains 7 placeholders that need
# values before the test will run meaningfully. Search the file for 'FILL IN'
# to find them. Each `Set-ItResult -Skipped` line is a stub waiting for a real
# assertion. PesterForge generated the structure; you supply the expected values.
# -----------------------------------------------------------------------------
Describe 'Read-ConfigFile' {
BeforeAll {
$script:scriptPath = Join-Path -Path $ProjectRoot -ChildPath 'Try-Catch-Function.ps1'
# ... (dependency-guarded dot-source, trimmed)
. $script:scriptPath
}
Context 'Static checks' {
It 'script file exists' {
$scriptPath | Should -Exist
}
It 'function declaration present in source' {
$content = Get-Content -Path $scriptPath -Raw
$content | Should -Match 'function Read-ConfigFile'
}
}
Context 'Parameter validation' {
It 'Path parameter is mandatory' {
$cmd = Get-Command Read-ConfigFile -ErrorAction SilentlyContinue
$p = $cmd.Parameters['Path']
$isMandatory = ($p.Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] }).Mandatory
$isMandatory | Should -Be $true
}
}
Context 'Success path' {
It 'returns a PSCustomObject -- FILL IN call with valid inputs' {
# AST inferred return type from explicit return statements.
# Once you fill in a valid call above, replace this skip with:
# $r | Should -BeOfType [pscustomobject]
Set-ItResult -Skipped -Because 'stub'
}
}
Context 'Error handling' {
It 'handles error categories from try/catch -- FILL IN' {
# Enumerate which categories the catch blocks actually map
# errors to and add one Mock + assertion per category.
Set-ItResult -Skipped -Because 'stub'
}
}
# ... (Integration / Acceptance / Repository stub contexts, trimmed)
}
The Path parameter is mandatory test is real: the generator read the [Parameter(Mandatory)] attribute off the AST, and that assertion passes as generated. The error-handling test stays a skipped stub, because the generator can see the try/catch but has no way to know what your catch blocks should return, so it skips instead of pretending to pass. The header counts the placeholders (7 in this run) and tells you what to search for.
By default, generation is static analysis only: no AI call, no network. If you want richer drafted assertions there's an explicit -AiEnrich switch, and when the AI step fails for any reason the generator falls back to the AST-only output.
The public surface is five commands.
New-PesterTests | Generates a ready-to-run Pester test file for a function or script |
Get-PesterCoverage | Scans a module or folder and reports which functions have tests and where the coverage gaps are |
Measure-FunctionTestability | Inspects a function and reports its parameters, outputs, error types, and how testable it is |
New-TestProject | Creates a standard test project folder layout |
Start-TestRun | Runs your tests with PesterForge's recommended settings |
At the prompt
PesterForge is a PowerShell module. Import it and the five commands above are available in your session. The example on this page is exactly that workflow.
From an AI agent, over MCP
PesterForge MCP exposes the same engine as tools for Claude Code and other MCP clients, so your agent can generate tests, audit coverage, profile functions, and run suites mid-conversation.
Inside the PowerShell 7 Workbench
The upcoming Workbench builds this in as a right-click: Generate Pester Tests on the script you have open. The Workbench is in development, not released yet.
Point it at one of your own functions and see what it drafts.