Pester - It block doesn't recognise function outside block.

So I have this function at the script scope level.

function Rule_Match ($rule) {
    $index = ((0..($expected_rules.nsg.Count-1)) | Where-Object {$expected_rules.nsg[$_] -eq $rule.NSG})
    $expected_rules[$index] | Where-Object {$_.name -eq $rule.name}
}

My Pester test:
Describe "Rule Comparison Tests" {
    It "NSG 1 outbound rule comparison test" {
        (Compare-Object (Rule_Match $existing_nsg_01_allow_subnet_outbound) $existing_nsg_01_allow_subnet_outbound `
        -Property $Properties -IncludeEqual).SideIndicator  | Should -match "=="
    }

Error I am getting on run:

  [-] NSG 1 outbound rule comparison test 14ms (8ms|5ms)
   RuntimeException: Cannot index into a null array.
   at <ScriptBlock>, /Users/.../Documents/scrap.ps1:126
   at global:Rule_Match, /Users/.../Documents/Scrap.ps1:126

In which file is the function defined and what is the pester version you are using ?

Hi Kvprasoon, I am using Pester 5.0 and Powershell 7.1.
The function is in the same script as the test. Here is the full script with the rest of the tests and multiple entries to the arrays cut out:

# # Get data from Cloud
    # Resource Groups we want to search relevant objects for.
    $rg = "client_dev_main_01", "client_dev_orc_01"
    
    # Function to take rule and create a dynamic object for easier comparison
    function NSG_Rule_List ($rule) {
        # Variables needed to create dynamic name for each rule.
        # Naming convention: existing_NSG.name_Rule.name
        $prefix     =   "existing_"
        $nsg        =   $rule.id.Split("/")[8]
        $rulename   =   $rule.name.Insert(0,'_')
        $newname    =   "$prefix$nsg$rulename"
        # Setting the values of the variable, we want to compare on all of these.
        Set-Variable -name "$newname" -Value ($rule | Select-Object `
        @{N="ResourceGroup"             ;E={$_.id.Split("/")[4]}},
        @{N="NSG"                       ;E={$_.id.Split("/")[8]}},
        @{N="Name"                      ;E={$_.Name}},
        @{N="Access"                    ;E={$_.Access}},
        @{N="DestinationAddressPrefix"  ;E={$_.DestinationAddressPrefix}},
        @{N="DestinationPortRange"      ;E={$_.DestinationPortRange}},
        @{N="Direction"                 ;E={$_.Direction}},
        @{N="Priority"                  ;E={$_.Priority}},
        @{N="Protocol"                  ;E={$_.Protocol}},
        @{N="SourceAddressPrefix"       ;E={$_.SourceAddressPrefix}},
        @{N="SourcePortRange"           ;E={$_.SourcePortRange}}) -Scope 1
        # Inform terminal of new variable created
        Write-Host "New Variable created '`$$newname'"
    }
    # For each NSG within Resource Group, pass rule object to function above
    $rg | ForEach-Object {
        (Get-aznetworksecuritygroup -name * -ResourceGroupName $_ | Get-AzNetworkSecurityRuleConfig) | ForEach-object {
            NSG_Rule_List -rule $_
        }
    }
    
    # # Manually created objects for comparison with cloud objects
    # Function to add a new index to an array. Array[index] will be used to compare expected config of an NSG rule with existing config of an NSG rule 
    function New_Rule {
        Param(
            [Parameter(Mandatory=$True)]
            [String]
            $ResourceGroup,
            [Parameter(Mandatory=$True)]
            [String]
            $NSG,
            [Parameter(Mandatory=$True)]
            [String]
            $Name,
            [Parameter(Mandatory=$True)]
            [ValidateSet("Allow", "Deny")]
            [String]
            $Access,
            [Parameter(Mandatory=$True)]
            [ValidateSet("Inbound", "Outbound")]
            [String]
            $Direction,
            [Parameter(Mandatory=$True)]
            [String]
            $DestinationAddressPrefix,
            [Parameter(Mandatory=$True)]
            [String]
            $DestinationPortRange,
            [Parameter(Mandatory=$True)]
            [ValidateRange(1, 4096)]
            [Int]
            $Priority,
            [Parameter(Mandatory=$True)]
            [ValidateSet("*", "Icmp", "Tcp", "Udp")]
            [String]
            $Protocol,
            [Parameter(Mandatory=$True)]
            [String]
            $SourceAddressPrefix,
            [Parameter(Mandatory=$True)]
            [String]
            $SourcePortRange
        )
        Process
        {
            [PSCustomObject]@{
                "ResourceGroup"             = $ResourceGroup
                "NSG"                       = $NSG
                "Name"                      = $Name
                "Access"                    = $Access
                "Direction"                 = $Direction
                "DestinationAddressPrefix"  = $DestinationAddressPrefix
                "DestinationPortRange"      = $DestinationPortRange
                "Priority"                  = $Priority
                "Protocol"                  = $Protocol
                "SourceAddressPrefix"       = $SourceAddressPrefix
                "SourcePortRange"           = $SourcePortRange
            }
        }
    }
    # New array, using function above to fill in manual variables
    $expected_rules = @()
    # client NSG 1
    $expected_rules += New_Rule -ResourceGroup "client_dev_main_01" -NSG "client_nsg_01"   -Name "allow_clientsubnet_outbound"  -Access "Allow" -DestinationAddressPrefix "10.0.1.0/24"     -DestinationPortRange "*"   -Direction "Outbound"   -Priority 200 -Protocol "*" -SourceAddressPrefix "10.0.1.0/24"      -SourcePortRange "*"
    
    # # Comparison Setup
    # Function to match existing_NSG(?)_Rule_name on NSG then on name of rule to get unique index for testing
    # Input is Cloud object (existing_NSG(?)_Rule_name), output is matching index on NSG and name from expected_rules array
    function Rule_Match ($rule) {
        # Before the Pipe, we just getting count of expected_rules then feeding 0-12 as an index and getting back only indexes where the NSG matches
        $index = ((0..($expected_rules.nsg.Count-1)) | Where-Object {$expected_rules.nsg[$_] -eq $rule.NSG})
        # Only on indexes where the NSG matches, we look for a match on rule name. Output in the index of array from expected_rules
        $expected_rules[$index] | Where-Object {$_.name -eq $rule.name}
    }
    # All the properties we compare on for the compare object tests
    $Properties = "ResourceGroup", "NSG", "Name", "Access", "DestinationAddressPrefix", "DestinationPortRange", "Direction", "Priority", "Protocol", "SourceAddressPrefix", "SourcePortRange"
    
    # # Testing
    # As mentioned earlier, function calls relevant index of expected_rules for comparison.
    # We are not matching same object with itself but $existing_NSG(?)_Rule_name with expected[index] where NSG and name match.
    Describe "Rule Comparison Tests" {
        It "client NSG 1 outbound rule comparison test" {
            (Compare-Object (Rule_Match $existing_client_nsg_01_allow_clientsubnet_outbound) $existing_client_nsg_01_allow_clientsubnet_outbound `
            -Property $Properties -IncludeEqual).SideIndicator  | Should -match "=="
        }

Pester v 5.x has significant changes from previous versions and your problem can be solved by re arranging the code. Please read GitHub - pester/Pester: Pester is the ubiquitous test and mock framework for PowerShell. to understand more on the changes which is causing you the trouble.

Thnx Kvprasoon, dumping everything into a large BeforeAll {} block except for the tests solved my issue. I’m new to coding, its a new concept for me to have a BeforeAll {} scriptblock.