powershellpester

How to MOCK a PowerShell cmdlet that appears multiple times with different purposes in a function using Pester?


I would like to know if it is possible to Mock the same cmdlet in a function that return different object data types?

Below shows the function that has Get-ADGroupMember cmdlet appearing 2 times. I would like both Get-ADGroupMember to return different objects & data types using Mock cmdlet.

I am expecting to have the first Get-ADGroupMember to return a System.Array object type & the second Get-ADGroupMember to return a PSCustomObject type.

Thanks in advance for helping.

I have tried to Mock the cmdlet using what I have researched from the web & Pester documentation but has found no success.

BeforeAll {
    function Get-Something 
    {
        ## Initialize an array for PSCustomObject
        $ArrayCustomObject = @()
        
        ## Return an Array Object
        $ArrayObject = (Get-ADGroupMember "AD_Group_Name").SamAccountName
    
        foreach ($Object in $ArrayObject)
        {
            ## Return a PSCustomObject + SamAccountName is not the same as the previous SamAccountName
            $PSCustomObject = Get-ADGroupMember $ADMember | Select-Object objectClass, SamAccountName 
    
            $ArrayCustomObject += $PSCustomObject
        }
    
        return $ArrayCustomObject
    }
}

Describe "Get-Something" {
    Context "MOCK Get-ADGroupMember" {
        BeforeAll {
            ## Would like to have the first cmdlet to be mock with this
            Mock -CommandName Get-ADGroupMember -MockWith {
                [PSCustomObject]@{SamAccountName = "AD_GROUP_1"},
                [PSCustomObject]@{SamAccountName = "AD_GROUP_2"}
            }
            ## Would like to have the second cmdlet to be mock with this
            Mock -CommandName Get-ADGroupMember -MockWith {
                [PSCustomObject]@{objectClass = "User"; SamAccountName = "USER_1"},
                [PSCustomObject]@{objectClass = "User"; SamAccountName = "USER_2"}
            }
        }

        It "Get-ADGroupMember should run 2 times" {
            Get-Something -ADGroupName "ANY_AD_NAME"
            Assert-MockCalled Get-ADGroupMember -Times 2
        }

        It "Should Match PSCustomObject" {
            (Get-Something -ADGroupName "ANY_AD_NAME").count | should -be 2
        }
    }
}

Here is the result:

Describing Get-Something
 Context MOCK Get-ADGroupMember
   [+] Get-ADGroupMember should run 2 times 25ms (22ms|3ms)
   [-] Should Match PSCustomObject 13ms (12ms|0ms)
    Expected 2, but got 4.
Tests completed in 137ms
Tests Passed: 1, Failed: 1, Skipped: 0 NotRun: 0

Solution

  • You can use -ParameterFilter on a Mock to scope it to a specific use of a cmdlet based on the parameters used with it. In your example we can make a Mock match just the first use of Get-ADGroupMember by having a parameter filter that checks $Identity is set to "AD_Group_Name". The other Mock we then don't use a Parameter Filter on and it will get used for the other usage.

    The following works (note I had to declare Get-ADGroupMember as a function in my example because I don't have that Module installed on my system at the moment):

    BeforeAll {
    
        function Get-ADGroupMember {
            param($Identity)
        }
    
        function Get-Something {
            ## Initialize an array for PSCustomObject
            $ArrayCustomObject = @()
            
            ## Return an Array Object
            $ArrayObject = (Get-ADGroupMember "AD_Group_Name").SamAccountName
        
            foreach ($Object in $ArrayObject) {
                ## Return a PSCustomObject + SamAccountName is not the same as the previous SamAccountName
                $PSCustomObject = Get-ADGroupMember $ADMember | Select-Object objectClass, SamAccountName 
        
                $ArrayCustomObject += $PSCustomObject
            }
        
            return $ArrayCustomObject
        }
    }
    
    Describe "Get-Something" {
        Context "MOCK Get-ADGroupMember" {
            BeforeAll {
                Mock -CommandName Get-ADGroupMember -ParameterFilter { $Identity -eq 'AD_Group_Name' } -MockWith {
                    [PSCustomObject]@{SamAccountName = "AD_GROUP_1" },
                    [PSCustomObject]@{SamAccountName = "AD_GROUP_2" }
                 
                } 
                Mock -CommandName Get-ADGroupMember -MockWith {
                    [PSCustomObject]@{objectClass = "User"; SamAccountName = "USER_1" },
                    [PSCustomObject]@{objectClass = "User"; SamAccountName = "USER_2" }     
                }
            }
    
            It "Get-ADGroupMember should run 3 times" {
                Get-Something -ADGroupName "ANY_AD_NAME"
                Assert-MockCalled Get-ADGroupMember -Times 3
            }
    
            It "Should Match PSCustomObject" {
                $Result = (Get-Something -ADGroupName "ANY_AD_NAME")
                
                $Result.count | should -be 4
                $Result.objectClass | Should -Contain 'User'
            }
        }
    }
    

    Also I changed the checks of your test. We expect Get-ADGroupMember to run 3 times, once outside the loop (where it returns 2 results) then twice in the loop (once for each result). And we expect the Count returned to be 4, because each time its called in the loop it returns 2 results, so 4 total. I added a further check to make sure we're getting the objectClass property at the end to prove the right Mock was being called.