I came across a utility function: Get-ParameterValues
, by @Jaykul, along with a slightly modified version by elovelan. I chose to use elovelan's version which doesn't declare parameters. I can confirm that, as far as I've tested at the command line, the function works as intended.
I'm incorporating the function into a private module that I'm writing as a general utility function (with due-attribution, of course—thanks @jaykul and elovelan!!), and as such, I am trying to write unit tests for it. However, when running the tests, I'm getting the error Cannot find a variable with the name 'PSBoundParameters'.
I have a feeling this is less to do with Pester and more to do with my understanding of PowerShell. I'm hoping someone here in the community may be able to help me.
The Pester test fails with the error Cannot find a variable with the name 'PSBoundParameters'.
The function under test can be found here. Here is a portion of the test code:
Feature file
Feature: Get-ParameterValues
As a PowerShell function author
In order to more easily deal with function parameters
I want to be able to automatically populate a dictionary
containing the supplied function arguments or the parameter's
default value if the argument was not supplied
So that I can reduce boilerplate code when dealing with
function parameters.
Background: A test function with parameters
Given a test function
"""
function global:Test {
[CmdletBinding()]
param(
[Parameter(Position = 0)]
$ValueWithNoDefault,
[Parameter(Position = 1)]
$ValueWithDefault = 'default',
[Parameter(Position = 2)]
$ValueWithNullDefault = $null
)
$Parameters = (Get-ParameterValues)
$Parameters
}
"""
Scenario Outline: Get-ParameterValues returns a dictionary with the expected key and value
Given the value '<Value>' is provided for the '<Parameter>' parameter
When 'global:Test' is invoked
Then the hashtable '<ContainsAKey>' a key for the parameter
And the resolved parameter value is '<ExpectedValue>'.
Examples: Examples using parameter ValueWithNoDefault
| Parameter | Value | Contains | ExpectedValue |
| ValueWithNoDefault | <No value> | does not contain | <Does Not Exist> |
| ValueWithNoDefault | `$null | contains | `$null |
| ValueWithNoDefault | { `$foo = 1 } | contains | { `$foo = 1 } |
| ValueWithNoDefault | `$false | contains | `$false |
| ValueWithNoDefault | `$true | contains | `$true |
| ValueWithNoDefault | "" | contains | "" |
| ValueWithNoDefault | 0 | contains | 0 |
| ValueWithNoDefault | @() | contains | @() |
| ValueWithNoDefault | 1 | contains | 1 |
Step Definitions
BeforeEachFeature {
Import-Module -Name "${pwd}\path-to\My-Module.psd1" -Scope Global -Force
filter global:ConvertFrom-TableCellValue {
Param(
[Parameter(Mandatory=$True, Position=0, ValueFromPipeline=$True)]
[AllowNull()]
[AllowEmptyString()]
$Value
)
Process {
if ($Value -eq '$null') {
$Value = $null
}
switch -Regex ($Value) {
'^"?\{\s*(?<ScriptBody>.*)\s*\}"?$' { $Value = [ScriptBlock]::Create($Matches.ScriptBody) }
'(?<StringValue>".*")' { $Value = ($Matches.StringValue -as [string]) }
'$?(?i:(?<Boolean>true|false))' { $Value = [Boolean]::Parse($Matches.Boolean) }
'^\d+$' { $Value = $Value -as [int] }
default { }
}
$Value
}
}
}
AfterEachFeature {
$Module = Get-Module -Name "My-Module"
if ($Module) {
$Module.Name | Remove-Module
}
if (Test-Path "Function:\global:ConvertTo-Parameter") {
Remove-Item Function:\global:ConvertTo-Parameter
}
}
Given "a test function" {
param ([string]$func)
Invoke-Expression $func
$f = Get-Item 'Function:\Test'
$f | Should -Not -BeNull
}
Given "the value '(?<Value>.*)' is provided for the '(?<Parameter>\S*)' parameter" {
param(
[object]$Value,
[string]$Parameter
)
$Value = ConvertFrom-TableCellValue $Value
if ($Value -ne "<No value>") {
$Context = @{ $Parameter = $Value }
} else {
$Context = @{}
}
}
When "'(?<CmdletName>.*)' is invoked" {
[CmdletBinding()]
Param ([string]$CmdletName)
# NOTE: I probably don't need the if block, but I added this during debugging of this issue...just-in-case.
# NOTE: The test fails in this step definition at this point when it executes the `Test` function which calls Get-ParameterValues.
if ($Context.Keys.Count -gt 0) {
$actual = &$CmdletName @Context
} else {
$actual = &$CmdletName
}
Write-Debug $actual.GetType().FullName
}
Then "the hashtable '(?<ContainsAKey>(does not contain|contains))' a key for the parameter" {
param([string]$ContainsAKey)
($Context.ContainsKey($Parameter) -and $actual.ContainsKey($Context.$Parameter)) | Should -Be ($ContainsAKey -eq 'contains')
}
Sample Test Run Output
psake version 4.7.4
Copyright (c) 2010-2017 James Kovacs & Contributors
Executing InstallDependencies
Executing Init
Executing Test
Testing all features in 'C:\src\git\My-Module\Specs\features' with tags: 'Get-ParameterValues'
Feature: Get-ParameterValues
As a PowerShell function author
In order to more easily deal with function parameters
I want to be able to automatically populate a dictionary
containing the supplied function arguments or the parameter's
default value if the argument was not supplied
So that I can reduce boilerplate code when dealing with
function parameters.
Scenario: Get-ParameterValues returns a dictionary with the expected key and value
Examples:Examples using parameter ValueWithNoDefault
[+] Given a test function 59ms
[+] Given the value '<No value>' is provided for the 'ValueWithNoDefault' parameter 187ms
[-] When 'global:Test' is invoked 48ms
at <ScriptBlock>, C:\src\git\My-Module\Public\Utility\Get-ParameterValues.ps1: line 74
74: $BoundParameters = Get-Variable -Scope 1 -Name PSBoundParameters -ValueOnly
From C:\src\git\My-Module\Specs\features\Utility\Get-ParameterValues.feature: line 35
Cannot find a variable with the name 'PSBoundParameters'.
[-] Then the hashtable 'does not contain' a key for the parameter 201ms
at <ScriptBlock>, C:\src\git\My-Module\Specs\features\steps\Utility\Get-ParameterValues.steps.ps1: line 38
38: ($Context.ContainsKey($Parameter) -and $actual.ContainsKey($Context.$Parameter)) | Should -Be ($ContainsAKey -eq 'contains')
From C:\src\git\My-Module\Specs\features\Utility\Get-ParameterValues.feature: line 36
You cannot call a method on a null-valued expression.
[?] And the resolved parameter value is '<Does Not Exist>'. 40ms
Could not find implementation for step!
At And, C:\src\git\My-Module\Specs\features\Utility\Get-ParameterValues.feature: line 37
Can anyone please tell me why the $PSBoundParameters
dictionary does not exist inside the Get-ParameterValues
function even though parameters are passed to the Test
function? (I have a feeling it may have something to do with PowerShell scoping rules, since the When
script block has "script" scope--which I tried to overcome by making sure that the Test
function was declared in the global
scope.)
Update
So, going down the path of this might be a PowerShell scope issue that I just don't understand, I modified my Gherkin Given
step as follows:
Given a test function
"""
function global:Test {
[CmdletBinding()]
param(
[Parameter(Position = 0)]
$ValueWithNoDefault,
[Parameter(Position = 1)]
$ValueWithDefault = 'default',
[Parameter(Position = 2)]
$ValueWithNullDefault = $null
)
$DebugPreference = 'Continue'
if ($PSBoundParameters) {
Write-Debug "`$PSBoundParameters = $(ConvertTo-JSON $PSBoundParameters)"
} else {
Write-Debug "Unable to find `$PSBoundParameters automatic variable."
}
$Parameters = (Get-ParameterValues)
$Parameters
}
"""
And here is the output from running the tests for the first two table entries (see the original feature file above):
psake version 4.7.4
Copyright (c) 2010-2017 James Kovacs & Contributors
Executing InstallDependencies
Executing Init
Executing Test
Testing all features in 'C:\src\git\My-Module\Specs\features' with tags: 'Get-ParameterValues'
Feature: Get-ParameterValues
As a PowerShell function author
In order to more easily deal with function parameters
I want to be able to automatically populate a dictionary
containing the supplied function arguments or the parameter's
default value if the argument was not supplied
So that I can reduce boilerplate code when dealing with
function parameters.
Scenario: Get-ParameterValues returns a dictionary with the expected key and value
Examples:Examples using parameter ValueWithNoDefault
[+] Given a test function 54ms
DEBUG: $Parameter: ValueWithNoDefault
DEBUG: $Value: <No value>
[+] Given the value '<No value>' is provided for the 'ValueWithNoDefault'
parameter 29ms
DEBUG: Cmdlet: Test
DEBUG: Context: {
}
DEBUG: $PSBoundParameters = {
}
[-] When 'Test' is invoked 47ms
at <ScriptBlock>, C:\src\git\My-Module\Public\Utility\Get-ParameterValues.ps1: line 74
74: $BoundParameters = Get-Variable -Scope 1 -Name PSBoundParameters -ValueOnly
From C:\src\git\My-Module\Specs\features\Utility\Get- ParameterValues.feature: line 41
Cannot find a variable with the name 'PSBoundParameters'.
DEBUG: Context: {
}
DEBUG: actual:
[+] Then the hashtable 'does not contain' a key for the parameter 61ms
[?] And the resolved parameter value is '<Does Not Exist>'. 30ms
Could not find implementation for step!
At And, C:\src\git\My-Module\Specs\features\Utility\Get-ParameterValues.feature: line 43
DEBUG: $Parameter: ValueWithNoDefault
DEBUG: $Value:
[+] Given the value '$null' is provided for the 'ValueWithNoDefault' parameter 14ms
DEBUG: Cmdlet: Test
DEBUG: Context: {
"ValueWithNoDefault": ""
}
DEBUG: $PSBoundParameters = {
"ValueWithNoDefault": ""
}
[-] When 'Test' is invoked 9ms
at <ScriptBlock>, C:\src\git\My-Module\Public\Utility\Get-ParameterValues.ps1: line 74
74: $BoundParameters = Get-Variable -Scope 1 -Name PSBoundParameters -ValueOnly
From C:\src\git\My-Module\Specs\features\Utility\Get-ParameterValues.feature: line 41
Cannot find a variable with the name 'PSBoundParameters'.
DEBUG: Context: {
"ValueWithNoDefault": ""
}
DEBUG: actual:
[-] Then the hashtable 'contains' a key for the parameter 20ms
at <ScriptBlock>, C:\src\git\My- Module\Specs\features\steps\Utility\Get-ParameterValues.steps.ps1: line 39
39: ($Context.ContainsKey('Parameter') -and
$actual.ContainsKey($Context.$Parameter)) | Should -Be ($ContainsAKey -eq 'contains')
From C:\src\git\My-Module\Specs\features\Utility\Get-ParameterValues.feature: line 42
Expected $true, but got $false.
[?] And the resolved parameter value is '`$null'. 5ms
Could not find implementation for step!
At And, C:\src\git\My-Module\Specs\features\Utility\Get-ParameterValues.feature: line 43
As shown, I "dumped" the $PSBoundParameters
collection from inside the Test
function created in the Given
step only if it is non-null/empty/is "truthy" (otherwise I would write a string saying it doesn't exist). In every case, even when $null
is assigned to the parameter, $PSBoundParameters
is defined and exists inside of the Test
function. However, once the call to Get-ParameterValues
is made, inside of that function an attempt is made to retrieve the $PSBoundParameters
variable from the parent scope (in this case, the Test
function—which has already been established that the variable exists in that scope). But yet, I get the error message from the Pester test step definition. Unfortunately, these results confuse me even more.