Is there any way to test functions in a PowerShell script without executing the script?

665 Views Asked by At

I would like to define stand-alone functions in my PowerShell script and be able to Pester test the functions without executing the rest of the script. Is there any way to do this without defining the functions in a separate file?

In the following pseudocode example, how do I test functionA or functionB without executing mainFunctionality?

script.ps1:

functionA
functionB
...
mainFunctionality 

script.Tests.ps1:

BeforeAll {
  . $PSScriptRoot/script.ps1 # This will execute the mainFunctionality, which is what I want to avoid
}
Describe 'functionA' { 
   # ... tests
}

I believe in Python, you can do this by wrapping your "mainFunctionality" inside this condition, so I am looking for something similar in Powershell.

if __name__ == '__main__':
    mainFunctionality

Ref: What does if __name__ == "__main__": do?

2

There are 2 best solutions below

3
On

Using the PowerShell Abstract Syntax Tree (AST) to just grab functionA and invoke it:

$ScriptBlock = {
    function functionA {
        Write-Host 'Do something A'
    }
    
    function functionB {
        Write-Host 'Do something A'
    }
    
    function mainFunctionality {
        # Do something
        functionA
        # Do something
        functionB
        # Do something
    }
    
    mainFunctionality
}

Using NameSpace System.Management.Automation.Language
$Ast = [Parser]::ParseInput($ScriptBlock, [ref]$null, [ref]$null) # or: ParseFile
$FunctionA = $Ast.FindAll({
    $Args[0] -is [ScriptBlockAst] -and $Args[0].Parent.Name -eq 'functionA'
}, $True)
Invoke-Expression $FunctionA.EndBlock

Do something A
0
On

You could use $MyInvocation.PSCommandPath to determine who invoked your script, it's the closest I can think of to Python's if __name__ == '__main__':.

This property will give you the absolute path of the caller, from there you can extract the script's name, i.e. with Path.GetFileName and after you can determine what you want to do, for example, call mainFunctionality if the caller's name equals to main.ps1 or call mainFunctionality if the caller's name is not equal to script.Tests.ps1, etc. Short demo below.

  • myScript.ps1
function A {
    "I'm function A"
}

function B {
    "I'm function B"
}

function mainFunctionality {
    "I'm function mainFunctionality"
}

A # Calls A
B # Calls B

# Call `mainFunctionality` only if my caller's name is `main.ps1`
if([System.IO.Path]::GetFileName($MyInvocation.PSCommandPath) -eq 'main.ps1') {
    mainFunctionality
}

Then if calling myScript.ps1 from main.ps1 you would see:

I'm function A
I'm function B
I'm function mainFunctionality

And if calling myScript.ps1 from anywhere else (console or other script with a different name) you would see:

I'm function A
I'm function B