windowspowershellpesterpester-5

Pester 5 variable scoping issue - BeforeDiscovery/It


Edit

The crux of the question is: how do I get access to variable(s) declared in a BeforeDiscovery block in my It blocks that are not passed through by the it -foreach $var construct?


I'm having difficulties adjusting to the Discovery/Run phase and Scoping of Variables in Pester 5

Background

We are moving servers and what I'm trying to test is

  1. that every share on serverA also exists on serverB.
  2. that every readable share on serverA is also readable on serverB.

Using Pester 5, below code runs as intented but to make it work, I have to retrieve the $toShares twice. Retrieving the shares in my actual tests is using a net view and is a fairly long running operaton.

  1. I have to retrieve $toShares in the Discovery phase to construct the $readableFromShares list
  2. I have to retrieve an identical $toShares in a BeforeAll block to have them available in the should exists test

Question

How can I best restructure my test so that I only need to retrieve the $toShares once?

Testcode

$PesterPreference = [PesterConfiguration]::Default
$PesterPreference.Output.Verbosity = 'Detailed'

function Test-Path {$True} # hide the real Test-Path function
Describe "Describe" -ForEach @(
    @{fromServer ='serverA'; toServer = 'serverB'}    
    @{fromServer ='serverC'; toServer = 'serverD'}    
    ){
    
    BeforeDiscovery {
        $fromShares = 'share1', 'share2', 'share3'
        # $toShares is needed here to construct the readableFromShares array
        $toShares = 'share1', 'share2'
        $readableFromShares = $fromShares | 
            Where-Object {$toShares -contains $_} | 
            Where-Object {Test-Path "\\$($fromServer)\$($_)"}
    }
     
    Context "<fromServer> samba shares should exist on <toServer>" { 
        BeforeAll {
            # the "same" $toShares is needed here to be available in the exist tests
            $toShares = 'share1', 'share2'
        }
        It "Does \\<toServer>\<_> exist" -ForEach $fromShares {
            $toShares -contains $_ | Should -Be $true
        }
    }

    Context "Readable <fromServer> samba shares should als be readable on <toServer>" {
        It "<_> is readable on <fromServer>. \\<toServer>\<_> should also be readable." -ForEach $readableFromShares {
            Test-Path "\\$($toServer)\$($_)"| Should -Be $True
        }
    }
}

Output including two deliberate failing tests

Output testcases


Edit

Testcase including

  1. Two from/to servers (io one)
  2. Different sharenames for each set of servers

Test

$PesterPreference = [PesterConfiguration]::Default
$PesterPreference.Output.Verbosity = 'Detailed'
function Test-Path {$True} # hides the real Test-Path
function Get-FromShares($fromServer) {if ($fromServer -eq 'serverA') { @('ABshare1', 'ABshare2', 'ABshare3') } else {@('XYshare1', 'XYshare2', 'XYshare3')}}
function Get-ToShares($toServer) {if ($toServer -eq 'serverB') { @('ABshare1', 'ABshare2') } else {@('XYshare1', 'XYshare2')}}

class Shares { static $toShares = @{} }
function Test-Path {$True} # hides the real Test-Path
Describe "Describe" -ForEach @(
    @{fromServer ='serverA'; toServer = 'serverB'}    
    @{fromServer ='serverX'; toServer = 'serverY'}    
    ){

    #It "Define shares" -TestCases @( 1 )  { class Shares { static [string[]]$toShares = @('share1', 'share2') } }
    
    BeforeDiscovery {
        $fromShares = Get-FromShares($fromServer)
        [Shares]::toShares =@{ $fromServer = Get-ToShares($toServer)}
        $toShares = [Shares]::toShares[$fromServer]
        $readableFromShares = $fromShares | 
            Where-Object {$toShares -contains $_} | 
            Where-Object {Test-Path "\\$($fromServer)\$($_)"}
    }
     
    Context "<fromServer> samba shares should exist on <toServer>" { 
        BeforeAll {            
            $toShares = [Shares]::toShares[$fromServer]
        }
        It "Does \\<toServer>\<_> exist" -ForEach $fromShares {
            $toShares -contains $_ | Should -Be $true
        }
    }

    Context "Readable <fromServer> samba shares should als be readable on <toServer>" {
        It "<_> is readable on <fromServer>. \\<toServer>\<_> should also be readable." -ForEach $readableFromShares {
            Test-Path "\\$($toServer)\$($_)"| Should -Be $True
        }
    }
} 

Solution

  • The solution is to enrich the variables passed to the -foreach clauses with whatever data you like to have available in the It blocks.

    In below example, the $fromShares array now contains an array of objects instead of a plain array of strings. Each object still contains the share name and also contains the $toShares array you need in your test.

    
    Describe "Describe" -ForEach @(
        @{fromServer ='serverA'; toServer = 'serverB'}    
        @{fromServer ='serverX'; toServer = 'serverY'}    
        ){
        
        BeforeDiscovery {
            $toShares = Get-ToShares($toServer)
            $fromShares = Get-FromShares($fromServer) | % {
                [PSCustomObject]@{
                    fromShare = $_
                    toShares = $toShares
                }
            }
    
            $readableFromShares = $fromShares | 
                Where-Object {$toShares -contains $_} | 
                Where-Object {Test-Path "\\$($fromServer)\$($_)"}
        }
         
        Context "<fromServer> samba shares should exist on <toServer>" { 
            BeforeAll {            
                $toShares = [Shares]::toShares[$fromServer]
            }
            It "Does \\<toServer>\<_.fromShare> exist" -ForEach $fromShares {
                $_.toShares -contains $_.fromShare | Should -Be $true
            }
        }
    
        Context "Readable <fromServer> samba shares should als be readable on <toServer>" {
            It "<_> is readable on <fromServer>. \\<toServer>\<_> should also be readable." -ForEach $readableFromShares {
                Test-Path "\\$($toServer)\$($_)"| Should -Be $True            
            }
        }
    }