scorpio
scorpio

Reputation: 1625

Pester Mock does not work for Invoke-Command using script block

I have a console logger

function Common-Write-Log-Console
{
    param (
        [Parameter(Mandatory=$true)]
        [string] $logText
        )

        $textToOutput = [String]::Format("{0}:{1}", [System.DateTime]::Now.ToString(), $logText)
        Write-Output ($textToOutput)
}

Then I have wrapper function which calls it by dynamically loading it

function Common-Write-Log-WithInvoke
{
    param (
        [Parameter(Mandatory=$true)]
        [string] $logText

        )

    foreach($logger in $loggers.Values)
    {
        Invoke-Command $logger -ArgumentList $logText,$verbosityLevel,$logType
    }

}

Another wrapper function which calls it directly

function Common-Write-Log-WithoutInvoke
{
    param (
        [Parameter(Mandatory=$true)]
        [string] $logText, 
        [string] $verbosityLevel = "Normal",
        [string] $logType = "Info"
        )

    Common-Write-Log-Console $logText

}

Add Loggers for dynamic calling

 $loggers = @{}
 $loggers.Add("Console_Logger", ${function:Common-Write-Log-Console})

Now I have couple of Pester tests

 # pester tests
Describe "Common-Write-Log" {
    It "Test 1. Calls all log sources when log sources are called directly - **this test passes**" {


        # Arrange
        $expectedLogText  = "test message" 
        Mock Common-Write-Log-Console -Verifiable -ParameterFilter { $logText -eq  $expectedLogText}

        # Act
        Common-Write-Log-WithoutInvoke "test message"

        # Assert
        Assert-VerifiableMocks
    }

    It "Test 2. Calls all log sources when log sources are called through Invoke-Command - **this test fails**" {


        # Arrange
        $expectedLogText  = "test message" 
        Mock Common-Write-Log-Console -Verifiable -ParameterFilter { $logText -eq  $expectedLogText}

        # Act
        Common-Write-Log-WithInvoke "test message"

        # Assert
        Assert-VerifiableMocks # This statement fails as actual function "Common-Write-Log-Console" is called instead of the mocked one
    }
}

Test 2. always fails. I have worked around by creating a fake logger function, instead of using mock and setting some global variables to verify/assert in my test that dynamic loading and calling of intended function works. It would be nice to get the Mock working in such scenario , rather then writing those dumb fakes!

Any ideas how would it work or is it not supported by pester at all?

PS: All code works if copied in order

Upvotes: 3

Views: 2540

Answers (1)

alx9r
alx9r

Reputation: 4263

Pester's Scope of Mocked Function Interception

Pester only intercepts calls to mocked functions in particular scopes. I think the only supported method of controlling this scope is using InModuleScope. That allows you to designate that Pester should intercept calls to mocked functions in the module that you have specified using InModuleScope.

Common-Write-Log-Console is Not Called in a Scope where Pester Intercepts

In "Test 2.", the "call" to Common-Write-Log-Console takes place somewhere inside this call:

Invoke-Command $logger -ArgumentList $logText,$verbosityLevel,$logType

You have not specified that Pester should intercept calls to mocked functions inside whatever module Invoke-Command is implemented. (I doubt that you could achieve this, because Invoke-Command is shipped with WMF and probably not implemented in PowerShell.)

Use the Call Operator Instead of Invoke-Command

When invoking PowerShell commands as delegates I recommend using the & call operator instead of Invoke-Command. If you rewrite this line

Invoke-Command $logger -ArgumentList $logText,$verbosityLevel,$logType

as

& $logger -logText $logText

Test 2, should call the mock for Common-Write-Log-Console as you desire.

A Handle to a PowerShell Delegate is Just a String Containing the Function Name

When invoking a PowerShell delegate, all you need is a string containing the name of the function. If you rewrite this line

$loggers.Add("Console_Logger", ${function:Common-Write-Log-Console})    

as

$loggers.Add("Console_Logger", 'Common-Write-Log-Console')

$logger will correctly contain the name of the command which the call operator can invoke.


I tested this on my computer and both tests now pass:

enter image description here

Upvotes: 4

Related Questions