Mocking Where-Object command with a ParameterFilter in Pester

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

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' }

Thanks Dave