Pester icon indicating copy to clipboard operation
Pester copied to clipboard

Testing of ForEach-Object -Parallel

Open JanElholm opened this issue 2 years ago • 7 comments

Checklist

Summary of the feature request

$TimeToSleep = 5
1..10 | ForEach-Object -Parallel {
    $TimeToSleep = $using:TimeToSleep
    Start-Sleep $TimeToSleep
} -ThrottleLimit 5

I would like to be able to mock a function inside of a "ForEach-Object -Parallel". In the above rather contrieved example I would like to be able to mock the Start-Sleep function

Thx

How should it work?

No response

JanElholm avatar May 24 '23 11:05 JanElholm

Unfortunately that's not possible.

Using -Parallel runs the scriptblock-code in new PowerShell runspaces (threads). The new runspaces doesn't share any state like imported modules, variables etc. with your normal session. So the Pester-module and any knowledge of existing mocks are invisble inside.

fflaten avatar May 24 '23 19:05 fflaten

Sorry to hear that it's not possible. Any recommendation or best practises on how to implement multithreading in powershell in order to make it testable with Pester?

JanElholm avatar May 26 '23 06:05 JanElholm

It's mostly mocking objects and commands that won't work.

You can test the results of any code using multithreading, so maybe focus on integration tests against test-data/environments?

fflaten avatar May 26 '23 12:05 fflaten

Running into this same limitation of Pester. Some easy repro code:

Describe 'it should be' {
    BeforeAll {
        $realCmdLet = @{
            InvokeCommand = Get-Command Invoke-Command
        }

        Mock Invoke-Command {
            & $realCmdLet.InvokeCommand -Scriptblock {
                1
            } -ComputerName $env:COMPUTERNAME
        }

        $testData = @(
            @{ComputerName = 'PC1' }
            @{ComputerName = 'PC2' }
        )
    }
    It 'green' {
        $testData | ForEach-Object {
            Invoke-Command -ComputerName $_.ComputerName -ScriptBlock { 1 }
        }

        Should -Invoke Invoke-Command -Times 1 -Exactly -ParameterFilter {
            $ComputerName -eq 'PC1'
        }

        Should -Invoke Invoke-Command -Times 1 -Exactly -ParameterFilter {
            $ComputerName -eq 'PC2'
        }
    }
    It 'green' {
        $testData | ForEach-Object -Parallel {
            Invoke-Command -ComputerName $_.ComputerName -ScriptBlock { 1 }
        }

        Should -Invoke Invoke-Command -Times 1 -Exactly -ParameterFilter {
            $ComputerName -eq 'PC1'
        }

        Should -Invoke Invoke-Command -Times 1 -Exactly -ParameterFilter {
            $ComputerName -eq 'PC2'
        }
    }
}

The last test fails because of Foreach-Object -Parallel creating its own runspaces in which Pester can't utilise mocks.

@nohwnd @fflaten is there really no way around this?

It would maybe be an idea if Pester could remove the -Parallel switch and run it in the same thread just for debugging purposes. But that would also entail replacing variables that start with $using:xxx.

Related StackOverflow question.

DarkLite1 avatar Feb 22 '24 09:02 DarkLite1