Ninja
Ninja

Reputation: 376

Pester 5 - Access Multiple Variables in Nested Loop

I am converting Pester tests from V4 to V5 and in line with the best practices, moving the loops from foreach() to Describe -ForEach{}. The tests are a standard set that checks if each function has comment based help and this requires multiple nested loops.

This question is similar to Pester 5 variable scoping and Working with nested foreach and I have referred to Data Driven Tests, but my problem goes deeper as firstly, I need multiple levels of nested loops and secondly, I need access to multiple variables. The first variable $helpParameterNames is used by the loop and the value of each row is checked to see if it exists in the second variable $parameterNames

V4 In V4, the arrays are accessible throughout the whole set of Describe/Context/It blocks so when I get to the last foreach loop, both $helpParameterNames and $parameterNames are accessible.

$commands = Get-Command -Module ModuleName

foreach ($command in $commands) {
    $commandName = $command.Name
    $help = Get-Help $commandName -ErrorAction SilentlyContinue
    
    Describe "Test help for $commandName" -Tag Help {
        
      <It Tests Removed>
        
        Context "Test parameter help for $commandName" {            
            $common = 'Debug', 'ErrorAction', 'ErrorVariable', 'InformationAction', 'InformationVariable', 'OutBuffer', 'OutVariable', 'PipelineVariable', 'Verbose', 'WarningAction', 'WarningVariable'
            $parameters = $command.ParameterSets.Parameters | Sort-Object -Property Name -Unique | Where-Object Name -notin $common
            $parameterNames = $parameters.Name
            $helpParameterNames = $help.Parameters.Parameter.Name | Sort-Object -Unique
            
            foreach ($parameter in $parameters) {
                $parameterName = $parameter.Name
                $parameterHelp = $help.parameters.parameter | Where-Object Name -EQ $parameterName               
                
                <It Tests Removed>
            }
            
            foreach ($helpParm in $helpParameterNames) {
                It "Should find parameter $helpParm in the function" {
                    $helpParm -in $parameterNames | Should Be $true
                }
            }
        }
    }

V5 In V5 as the setup code has to be defined in the BeforeDiscovery block, I can't get it to be available in the sub-blocks automatically. I can make it accessible by passing the array into the -ForEach as a HashTable, but then it will treat it as 1 object and not iterate over it, which is fine for $help, but not for $helpParameterNames

BeforeDiscovery {
    $commands = Get-Command -Module ModuleName
}

Describe "Help Rules" -Tags Build, Help -ForEach $commands {
    BeforeDiscovery {
        $commandName = $_.Name

        $common = 'Debug', 'ErrorAction', 'ErrorVariable', 'InformationAction', 'InformationVariable', 'OutBuffer', 'OutVariable', 'PipelineVariable', 'Verbose', 'WarningAction', 'WarningVariable'
        $parameters = (Get-Command $commandName).ParameterSets.Parameters | Where-Object -Property Name -NotIn $common | Sort-Object -Property Name -Unique
        $parameterNames = $parameters.Name
        $help = Get-Help $commandName -Full -ErrorAction SilentlyContinue
    }

    Context "General Rules for $commandName" -ForEach @{help = $help} {

        <It Tests Removed>

        Describe "Parameter Rules for <parameter.Name>" -Tags Help, Parameter -ForEach $parameters {
        
            BeforeAll {
                $parameter = $_.parameters
                $helpParameterNames = $help.Parameters.Parameter.Name | Sort-Object -Unique
                $parameterHelp = $help.parameters.parameter | Where-Object Name -EQ $parameter.Name
            }

            <It Tests Removed>
        }

        It "Should find parameter <helpParameter> in the function" -Tag Help, Parameter -ForEach $helpParameterNames {
            
            BeforeAll {
                $helpParameter = $_
            }

            $helpParameter -in $parameterNames | Should -BeTrue
        }
    }

How do I get the last -ForEach to have access to both $parameterNames from the BeforeDiscovery block and also $helpParameterNames from the parent loop?

Upvotes: 0

Views: 414

Answers (1)

Ninja
Ninja

Reputation: 376

This is a unique situation and the solution is that some code has to be repeated both in BeforeDiscovery and BeforeAll. Adding the code to BeforeDiscovery means that it can be used in the Context -ForEach and adding the code to BeforeAll means that it is accessible as a single object inside the child Describe blocks.

Describe "Help Rules" -Tags Build, Help -ForEach $commands {

    BeforeDiscovery {
        $common = 'Debug', 'ErrorAction', 'ErrorVariable', 'InformationAction', 'InformationVariable', 'OutBuffer', 'OutVariable', 'PipelineVariable', 'Verbose', 'WarningAction', 'WarningVariable'
        $parameters = (Get-Command $_.Name).ParameterSets.Parameters | Where-Object -Property Name -NotIn $common | Sort-Object -Property Name -Unique
        $help = Get-Help $_.Name -Full -ErrorAction SilentlyContinue
        $helpParameterNames = $help.Parameters.Parameter.Name | Sort-Object -Unique
    }
    
    BeforeAll {
        $commandName = $_.Name

        $help = Get-Help $commandName -Full -ErrorAction SilentlyContinue
        $helpParameterNames = $help.Parameters.Parameter.Name | Sort-Object -Unique
        
        #Define this both in BeforeDiscovery and BeforeAll as it is needed both in the ForEach and also as an array
        $common = 'Debug', 'ErrorAction', 'ErrorVariable', 'InformationAction', 'InformationVariable', 'OutBuffer', 'OutVariable', 'PipelineVariable', 'Verbose', 'WarningAction', 'WarningVariable'
        $parameters = (Get-Command $_.Name).ParameterSets.Parameters | Where-Object -Property Name -NotIn $common | Sort-Object -Property Name -Unique
        $parameterNames = $parameters.Name
    }

    Context "General Rules for <commandName>" -ForEach $parameters {

Upvotes: 0

Related Questions