I noticed something peculiar when using Should -Invoke on a Mock command. When running without a Break statement in the Code Block, Should -Invoke works perfectly fine; but when running with a Break statement, Should -Invoke will always validate as TRUE, that is, Pester will pass all tests.
I do not want to remove Break statement from my original .ps1 script as I need this. Therefore, I would like to know if there is a way that I can Mock Break statement using pester or if there are any workaround for this, so that i can continue to validate all Mock commands without removing Break statement from my source code.
Below is the code that I used to show the behavior of Break statement given in the else code block when running with Pester. Notice that Context VALIDATE ELSE BLOCK passes all tests.
BeforeAll {
function Something-Function {
Param(
[Parameter(Mandatory)][String]$SamplePath
)
Write-Host "Hello from Function"
if (Test-Path -Path $SamplePath -PathType Leaf) {
Write-Host "Hello from If"
"Hello Earth!" | Out-File $SamplePath -Encoding utf8 -Append
}
else {
Write-Host "Hello from Else"
"Hello World!!" | Out-File $SamplePath -Encoding utf8 -Append
Break
}
}
}
Describe "Something-Function" {
BeforeAll {
Mock Write-Host {}
$TestPath = "TestDrive:\"
$FileName = "fake.txt"
}
Context "VALIDATE IF BLOCK" {
BeforeAll {
Mock Test-Path -MockWith {return $true}
# New-Item -ItemType File -Path $($TestPath + $FileName) -Force
"Hello World!!" | Out-File $($TestPath + $FileName) -Encoding utf8 -Append
}
It "Should -Invoke Write-Host -Exactly -Times 1" {
Something-Function -SamplePath $($TestPath + $FileName)
Should -Invoke Write-Host -Exactly -Times 1
}
It "Should -Invoke Write-Host -Exactly -Times 2" {
Something-Function -SamplePath $($TestPath + $FileName)
Should -Invoke Write-Host -Exactly -Times 2
}
}
Context "VALIDATE ELSE BLOCK" {
BeforeAll {
Mock Test-Path -MockWith {return $false}
}
It "Should -Invoke Write-Host -Exactly -Times 1" {
Something-Function -SamplePath $($TestPath + $FileName)
Should -Invoke Write-Host -Exactly -Times 1
}
It "Should -Invoke Write-Host -Exactly -Times 2" {
Something-Function -SamplePath $($TestPath + $FileName)
Should -Invoke Write-Host -Exactly -Times 2
}
It "Should -Invoke Write-Host -Exactly -Times 3" {
Something-Function -SamplePath $($TestPath + $FileName)
Should -Invoke Write-Host -Exactly -Times 3
}
It "Should -Invoke Out-File -Exactly -Times 50" {
Something-Function -SamplePath $($TestPath + $FileName)
Should -Invoke Write-Host -Exactly -Times 50
}
It "Should -Invoke Import-Csv -Exactly -Times 100" {
Something-Function -SamplePath $($TestPath + $FileName)
Should -Invoke Import-Csv -Exactly -Times 100
}
}
}
# PS C:\user\repo> Invoke-Pester -Output Detailed .\TEST\Issue.tests.ps1
# Pester v5.3.3
# Starting discovery in 1 files.
# Discovery found 7 tests in 199ms.
# Running tests.
# Running tests from 'C:\user\repo\TEST\Issue.tests.ps1'
# Describing Something-Function
# Context VALIDATE IF BLOCK
# [-] Should -Invoke Write-Host -Exactly -Times 1 96ms (93ms|3ms)
# Expected Write-Host to be called 1 times exactly but was called 2 times
# at Should -Invoke Write-Host -Exactly -Times 1, C:\user\repo\TEST\Issue.tests.ps1:42
# at <ScriptBlock>, C:\user\repo\TEST\Issue.tests.ps1:42
# [+] Should -Invoke Write-Host -Exactly -Times 2 34ms (34ms|0ms)
# Context VALIDATE ELSE BLOCK
# [+] Should -Invoke Write-Host -Exactly -Times 1 69ms (66ms|3ms)
# [+] Should -Invoke Write-Host -Exactly -Times 2 35ms (34ms|0ms)
# [+] Should -Invoke Write-Host -Exactly -Times 3 33ms (33ms|0ms)
# [+] Should -Invoke Out-File -Exactly -Times 50 35ms (34ms|0ms)
# [+] Should -Invoke Import-Csv -Exactly -Times 100 35ms (34ms|1ms)
# Tests completed in 739ms
# Tests Passed: 6, Failed: 1, Skipped: 0 NotRun: 0
I have try mocking Break statement Mock Break {} but failed and generate the below error.
[-] Context Something-Function.VALIDATE ELSE BLOCK failed
CommandNotFoundException: Could not find Command Break
If you follow the link in @mklement0's comment above you'll see this:
The following bad code demonstrates this in action:
which gives this output:
Note that we don't see
inner afterorouter afterin the output - in short, thebreakinside theTest-Inneractually terminates theswitchcase insideTest-Outerand execution skips to the code after theswitchblock.You're effectively controlling the flow of the calling code from within
Test-Inner, which might work for your current script but it breaks the encapsulation of functions and as you can see from the documentation it's not recommended. A side effect is that trying to testTest-Innerwith Pester results inTest-Innerunexpectedly terminating the calling Pester test harness...A better approach might be for your
Test-Outerfunction to decide whether to callbreakto control its own flow based on a result returned fromTest-Inner:We still see the same output from the second example...
... but now the control flow (
break) is local to the code where the control statements (switch) live and you can callTest-Innerwithout thebreakbubbling up to the caller which makes it safe to execute from other callers (e.g. Pester).Workaround
If you really can't live with moving the
breakout ofTest-Inneryou could wrap the call in a sacrificial control statement so that gets terminated rather than the main caller code.For example, this version of
Test-Outerdoesn't exit theswitchcase whenTest-Innerinvokesbreakbecause it terminates the sacrificialforinstead...This now outputs the following:
...but it means you'll have to add the workaround at every call-site - for example in your Pester tests it would become this:
which will get messy very quickly, and overall it's probably better to invest time in cleaning up your script rather than making more convoluted tests to work around the funky
breakin the main script...