PesterForge

Write the test file.
Or don't.

PesterForge reads your PowerShell source and drafts the Pester test file for you. What it can prove from the code, it asserts for real. What it can’t, it marks FILL IN as a skipped stub, waiting on you to supply the expected values.

01 The function, then the test file

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.

02 The five commands

The public surface is five commands.

New-PesterTestsGenerates a ready-to-run Pester test file for a function or script
Get-PesterCoverageScans a module or folder and reports which functions have tests and where the coverage gaps are
Measure-FunctionTestabilityInspects a function and reports its parameters, outputs, error types, and how testable it is
New-TestProjectCreates a standard test project folder layout
Start-TestRunRuns your tests with PesterForge's recommended settings
03 Works how you already work

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.

How the MCP works →

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.

The Workbench story →

04 Next step

Point it at one of your own functions and see what it drafts.

Read the quick start