Mocking Where-Object command with a ParameterFilter in Pester

Tagged: 

This topic contains 2 replies, has 2 voices, and was last updated by Profile photo of Robbie Courtney Robbie Courtney 4 months, 2 weeks ago.

  • Author
    Posts
  • #47672
    Profile photo of Robbie Courtney
    Robbie Courtney
    Participant

    Since I cannot Mock “[ADSI]”WinNt://$C” at least to the best of my knowledge with Pester without throwing it into a function itself and then mocking that function. I am trying to mock the Where-Object Cmdlet with a parameter filter using Pester. However I have not had any luck. I've tried everything I can think of and cannot get it to work. I suspect that it is an issue of the parameter taking a scriptblock.

    Below is a basic example of what I'm trying to do. The end result is I want to mock a specific Where-Object Cmdlet to feed in fake testdata to test some other logic. If anyone can give me some pointers I would appreciate it.

    Function I'm trying to run Pester test on

    function Get-LocalUser {
        [CmdletBinding()]
        Param(
            [Parameter(Mandatory=$true,
                       ValueFromPipeline=$true,
                       ValueFromPipelineByPropertyName=$true)]
            [String[]]$Name,
            [Parameter(ValueFromPipelineByPropertyName=$true)]
            [String[]]$ComputerName = $env:COMPUTERNAME 
        )
        Process 
        { 
            foreach ($C in $ComputerName) 
            { # Do this for each computer in the $ComputerName collection
                Write-Verbose "Retrieving users on `"$C`"" 
                
                $objOu = [ADSI]"WinNT://$C"
                $localUsers = $objOu.psbase.Children | Where-Object { $_.psbase.SchemaClassName -match 'user' }
    
                foreach ($n in $Name) 
                {
                    $users = $localUsers | Where-Object { $_.name -like $n }
                    
                    # if a wildcard is used there may be more than one result, need to go through each one
                    foreach ($u in $users) 
                    {
                        #ToDo: Add all properties to object and use format view to filter displayed data
                        $Obj = New-Object -TypeName PSObject -Property @{'Name' = $u.name.ToString();
                                                                        'Description' = $u.description.ToString()}
                        Write-Output $Obj
                    }
                }                  
            }
        }
    }
    

    Pester Test

    $here = Split-Path -Parent $MyInvocation.MyCommand.Path
    $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.", ".")
    . "$here\$sut"
    
    #helper function
    function createUsers([String[]]$User)
    { 
        $userList = @()
    
        foreach ($u in $User) {
            $userList += [pscustomobject]@{ name = "$u"; description = "$u Account";}
        }
    
        $userList
    }
    Describe "Get-LocalUser" {
        
        Context "Pipeline input" {
            $user1 = "test1"
            $user2 = "test2"
        
            Mock Where-Object { createUsers -User $user1, $user2 } -ParameterFilter { $FilterScript -eq "{ `$_.psbase.SchemaClassName -match 'user' }" }
       
            $result = 't*' | Get-LocalUser
        
            It "Test to see of mock was called..." {
                Assert-MockCalled Where-Object -Exactly 1 {} -ParameterFilter { $FilterScript -eq "{ `$_.psbase.SchemaClassName -match 'user' }" }
            }
        }
    

    I verify that the mock never gets called by using the Assert-Mockcalled test

  • #47686
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    That's a little bit tricky. ScriptBlock objects are reference types, so the -eq operator doesn't help you much if you try to compare them directly:

    {} -eq {} # False
    

    What you'll probably need to do is convert the parameter to a string inside your filter, and do a test on the string representation. (Note that when you do this, the curly braces are gone from the string).

    -ParameterFilter { [string]$FilterScript -eq " `$_.psbase.SchemaClassName -match 'user' " }
    

    That can be pretty fragile, though; whitespace will throw it off, etc. Personally, I would just change the code slightly to make it more easily tested. These two lines could go into their own function, for example, and then you could just mock that function in your tests without a filter:

                $objOu = [ADSI]"WinNT://$C"
                $localUsers = $objOu.psbase.Children | Where-Object { $_.psbase.SchemaClassName -match 'user' }
    
  • #47891
    Profile photo of Robbie Courtney
    Robbie Courtney
    Participant

    Thanks Dave

You must be logged in to reply to this topic.