diff --git a/global.json b/global.json index 04fa23b25..786bc6556 100644 --- a/global.json +++ b/global.json @@ -4,4 +4,4 @@ "version": "8.0.100", "allowPrerelease": false } -} \ No newline at end of file +} diff --git a/src/Module.ps1 b/src/Module.ps1 index 4eb844f74..34d86885f 100644 --- a/src/Module.ps1 +++ b/src/Module.ps1 @@ -84,6 +84,9 @@ $script:SafeCommands['Set-DynamicParameterVariable'] = $ExecutionContext.Session 'Should-BeLikeString' 'Should-NotBeLikeString' + 'Should-Invoke' + 'Should-NotInvoke' + 'Should-BeFasterThan' 'Should-BeSlowerThan' 'Should-BeBefore' diff --git a/src/Pester.psd1 b/src/Pester.psd1 index 48d9eb5b9..7d574845a 100644 --- a/src/Pester.psd1 +++ b/src/Pester.psd1 @@ -16,7 +16,7 @@ CompanyName = 'Pester' # Copyright statement for this module - Copyright = 'Copyright (c) 2024 by Pester Team, licensed under Apache 2.0 License.' + Copyright = 'Copyright (c) 2025 by Pester Team, licensed under Apache 2.0 License.' # Description of the functionality provided by this module Description = 'Pester provides a framework for running BDD style Tests to execute and validate PowerShell commands inside of PowerShell and offers a powerful set of Mocking Functions that allow tests to mimic and mock the functionality of any command inside of a piece of PowerShell code being tested. Pester tests can execute any command or script that is accessible to a pester test file. This can include functions, Cmdlets, Modules and scripts. Pester can be run in ad hoc style in a console or it can be integrated into the Build scripts of a Continuous Integration system.' @@ -106,6 +106,9 @@ 'Should-BeLikeString' 'Should-NotBeLikeString' + 'Should-Invoke' + 'Should-NotInvoke' + 'Should-BeFasterThan' 'Should-BeSlowerThan' 'Should-BeBefore' diff --git a/src/functions/Pester.SessionState.Mock.ps1 b/src/functions/Pester.SessionState.Mock.ps1 index aa4b97a48..d592f7ec4 100644 --- a/src/functions/Pester.SessionState.Mock.ps1 +++ b/src/functions/Pester.SessionState.Mock.ps1 @@ -648,7 +648,7 @@ function Should-InvokeVerifiable ([switch] $Negate, [string] $Because) { Set-ShouldOperatorHelpMessage -OperatorName InvokeVerifiable ` -HelpMessage 'Checks if any Verifiable Mock has not been invoked. If so, this will throw an exception.' -function Should-Invoke { +function Should-InvokeAssertion { <# .SYNOPSIS Checks if a Mocked command has been called a certain number of times @@ -966,7 +966,7 @@ function Should-Invoke { & $script:SafeCommands['Add-ShouldOperator'] -Name Invoke ` -InternalName Should-Invoke ` - -Test ${function:Should-Invoke} + -Test ${function:Should-InvokeAssertion} Set-ShouldOperatorHelpMessage -OperatorName Invoke ` -HelpMessage 'Checks if a Mocked command has been called a certain number of times and throws an exception if it has not.' @@ -1135,28 +1135,28 @@ function Invoke-Mock { # using @() to always get array. This avoids null error in Invoke-MockInternal when no behaviors where found (if-else unwraps the lists) $behaviors = @(if ($targettingAModule) { - # we have default module behavior add it to the filtered behaviors if there are any - if ($null -ne $moduleDefaultBehavior) { - $moduleBehaviors.Add($moduleDefaultBehavior) + # we have default module behavior add it to the filtered behaviors if there are any + if ($null -ne $moduleDefaultBehavior) { + $moduleBehaviors.Add($moduleDefaultBehavior) + } + else { + # we don't have default module behavior add the default non-module behavior if we have any + if ($null -ne $nonModuleDefaultBehavior) { + $moduleBehaviors.Add($nonModuleDefaultBehavior) + } + } + + $moduleBehaviors } else { - # we don't have default module behavior add the default non-module behavior if we have any + # we are not targeting a mock in a module use the non module behaviors if ($null -ne $nonModuleDefaultBehavior) { - $moduleBehaviors.Add($nonModuleDefaultBehavior) + # add the default non-module behavior if we have any + $nonModuleBehaviors.Add($nonModuleDefaultBehavior) } - } - - $moduleBehaviors - } - else { - # we are not targeting a mock in a module use the non module behaviors - if ($null -ne $nonModuleDefaultBehavior) { - # add the default non-module behavior if we have any - $nonModuleBehaviors.Add($nonModuleDefaultBehavior) - } - $nonModuleBehaviors - }) + $nonModuleBehaviors + }) $callHistory = (Get-MockDataForCurrentScope).CallHistory diff --git a/src/functions/assert/Mock/Should-Invoke.ps1 b/src/functions/assert/Mock/Should-Invoke.ps1 new file mode 100644 index 000000000..3f729cbf1 --- /dev/null +++ b/src/functions/assert/Mock/Should-Invoke.ps1 @@ -0,0 +1,199 @@ +function Should-Invoke { + <# + .SYNOPSIS + Checks if a Mocked command has been called a certain number of times + and throws an exception if it has not. + + .DESCRIPTION + This command verifies that a mocked command has been called a certain number + of times. If the call history of the mocked command does not match the parameters + passed to Should-Invoke, Should-Invoke will throw an exception. + + .PARAMETER CommandName + The mocked command whose call history should be checked. + + .PARAMETER ModuleName + The module where the mock being checked was injected. This is optional, + and must match the ModuleName that was used when setting up the Mock. + + .PARAMETER Times + The number of times that the mock must be called to avoid an exception + from throwing. + + .PARAMETER Exactly + If this switch is present, the number specified in Times must match + exactly the number of times the mock has been called. Otherwise it + must match "at least" the number of times specified. If the value + passed to the Times parameter is zero, the Exactly switch is implied. + + .PARAMETER ParameterFilter + An optional filter to qualify which calls should be counted. Only those + calls to the mock whose parameters cause this filter to return true + will be counted. + + .PARAMETER ExclusiveFilter + Like ParameterFilter, except when you use ExclusiveFilter, and there + were any calls to the mocked command which do not match the filter, + an exception will be thrown. This is a convenient way to avoid needing + to have two calls to Should-Invoke like this: + + Should-Invoke SomeCommand -Times 1 -ParameterFilter { $something -eq $true } + Should-Invoke SomeCommand -Times 0 -ParameterFilter { $something -ne $true } + + .PARAMETER Scope + An optional parameter specifying the Pester scope in which to check for + calls to the mocked command. For RSpec style tests, Should-Invoke will find + all calls to the mocked command in the current Context block (if present), + or the current Describe block (if there is no active Context), by default. Valid + values are Describe, Context and It. If you use a scope of Describe or + Context, the command will identify all calls to the mocked command in the + current Describe / Context block, as well as all child scopes of that block. + + .PARAMETER Because + The reason why the mock should be called. + + .PARAMETER Verifiable + Makes sure that all verifiable mocks were called. + + .EXAMPLE + ```powershell + Mock Set-Content {} + + {... Some Code ...} + + Should-Invoke Set-Content + ``` + + This will throw an exception and cause the test to fail if Set-Content is not called in Some Code. + + .EXAMPLE + ```powershell + Mock Set-Content -parameterFilter {$path.StartsWith("$env:temp\")} + + {... Some Code ...} + + Should-Invoke Set-Content 2 { $path -eq "$env:temp\test.txt" } + ``` + + This will throw an exception if some code calls Set-Content on $path=$env:temp\test.txt less than 2 times + + .EXAMPLE + ```powershell + Mock Set-Content {} + + {... Some Code ...} + + Should-Invoke Set-Content 0 + ``` + + This will throw an exception if some code calls Set-Content at all + + .EXAMPLE + Mock Set-Content {} + + {... Some Code ...} + + Should-Invoke Set-Content -Exactly 2 + + This will throw an exception if some code does not call Set-Content Exactly two times. + + .EXAMPLE + ```powershell + Describe 'Should-Invoke Scope behavior' { + Mock Set-Content { } + + It 'Calls Set-Content at least once in the It block' { + {... Some Code ...} + + Should-Invoke Set-Content -Exactly 0 -Scope It + } + } + ``` + + Checks for calls only within the current It block. + + .EXAMPLE + ```powershell + Describe 'Describe' { + Mock -ModuleName SomeModule Set-Content { } + + {... Some Code ...} + + It 'Calls Set-Content at least once in the Describe block' { + Should-Invoke -ModuleName SomeModule Set-Content + } + } + ``` + + Checks for calls to the mock within the SomeModule module. Note that both the Mock + and Should-Invoke commands use the same module name. + + .EXAMPLE + ```powershell + Should-Invoke Get-ChildItem -ExclusiveFilter { $Path -eq 'C:\' } + ``` + + Checks to make sure that Get-ChildItem was called at least one time with + the -Path parameter set to 'C:\', and that it was not called at all with + the -Path parameter set to any other value. + + .NOTES + The parameter filter passed to Should-Invoke does not necessarily have to match the parameter filter + (if any) which was used to create the Mock. Should-Invoke will find any entry in the command history + which matches its parameter filter, regardless of how the Mock was created. However, if any calls to the + mocked command are made which did not match any mock's parameter filter (resulting in the original command + being executed instead of a mock), these calls to the original command are not tracked in the call history. + In other words, Should-Invoke can only be used to check for calls to the mocked implementation, not + to the original. + + .LINK + https://pester.dev/docs/commands/Should-Invoke + + .LINK + https://pester.dev/docs/assertions + #> + [CmdletBinding(DefaultParameterSetName = 'Default')] + param( + [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'Default')] + [string]$CommandName, + + [Parameter(Position = 1, ParameterSetName = 'Default')] + [int]$Times = 1, + + [parameter(ParameterSetName = 'Default')] + [ScriptBlock]$ParameterFilter = { $True }, + + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'ExclusiveFilter', Mandatory = $true)] + [scriptblock] $ExclusiveFilter, + + [Parameter(ParameterSetName = 'Default')] + [string] $ModuleName, + [Parameter(ParameterSetName = 'Default')] + [string] $Scope = 0, + [Parameter(ParameterSetName = 'Default')] + [switch] $Exactly, + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'Verifiable')] + [string] $Because, + + [Parameter(ParameterSetName = 'Verifiable')] + [switch] $Verifiable + ) + + if ($PSBoundParameters.ContainsKey('Verifiable')) { + $PSBoundParameters.Remove('Verifiable') + $testResult = Should-InvokeVerifiable @PSBoundParameters + Test-AssertionResult $testResult + return + } + + # Maps the parameters so we can internally use functions that is + # possible to register as Should operator. + $PSBoundParameters["ActualValue"] = $null + $PSBoundParameters["Negate"] = $false + $PSBoundParameters["CallerSessionState"] = $PSCmdlet.SessionState + $testResult = Should-InvokeAssertion @PSBoundParameters + + Test-AssertionResult $testResult +} diff --git a/src/functions/assert/Mock/Should-NotInvoke.ps1 b/src/functions/assert/Mock/Should-NotInvoke.ps1 new file mode 100644 index 000000000..dc751190c --- /dev/null +++ b/src/functions/assert/Mock/Should-NotInvoke.ps1 @@ -0,0 +1,187 @@ +function Should-NotInvoke { + <# + .SYNOPSIS + Checks that mocked command was not called and throws exception if it was. + + .DESCRIPTION + This command verifies that a mocked command has not been called a certain number + of times. If the call history of the mocked command does not match the parameters + passed to Should-NotInvoke, Should-NotInvoke will throw an exception. + + .PARAMETER CommandName + The mocked command whose call history should be checked. + + .PARAMETER ModuleName + The module where the mock being checked was injected. This is optional, + and must match the ModuleName that was used when setting up the Mock. + + .PARAMETER Times + The number of times that the mock must be called to avoid an exception + from throwing. + + .PARAMETER Exactly + If this switch is present, the number specified in Times must match + exactly the number of times the mock has been called. Otherwise it + must match "at least" the number of times specified. If the value + passed to the Times parameter is zero, the Exactly switch is implied. + + .PARAMETER ParameterFilter + An optional filter to qualify which calls should be counted. Only those + calls to the mock whose parameters cause this filter to return true + will be counted. + + .PARAMETER ExclusiveFilter + Like ParameterFilter, except when you use ExclusiveFilter, and there + were any calls to the mocked command which do not match the filter, + an exception will be thrown. This is a convenient way to avoid needing + to have two calls to Should-NotInvoke like this: + + Should-NotInvoke SomeCommand -Times 1 -ParameterFilter { $something -eq $true } + Should-NotInvoke SomeCommand -Times 0 -ParameterFilter { $something -ne $true } + + .PARAMETER Scope + An optional parameter specifying the Pester scope in which to check for + calls to the mocked command. For RSpec style tests, Should-NotInvoke will find + all calls to the mocked command in the current Context block (if present), + or the current Describe block (if there is no active Context), by default. Valid + values are Describe, Context and It. If you use a scope of Describe or + Context, the command will identify all calls to the mocked command in the + current Describe / Context block, as well as all child scopes of that block. + + .PARAMETER Because + The reason why the mock should be called. + + .PARAMETER Verifiable + Makes sure that all verifiable mocks were called. + + .EXAMPLE + Mock Set-Content {} + + {... Some Code ...} + + Should-NotInvoke Set-Content + + This will throw an exception and cause the test to fail if Set-Content is not called in Some Code. + + .EXAMPLE + Mock Set-Content -parameterFilter {$path.StartsWith("$env:temp\")} + + {... Some Code ...} + + Should-NotInvoke Set-Content 2 { $path -eq "$env:temp\test.txt" } + + This will throw an exception if some code calls Set-Content on $path=$env:temp\test.txt less than 2 times + + .EXAMPLE + Mock Set-Content {} + + {... Some Code ...} + + Should-NotInvoke Set-Content 0 + + This will throw an exception if some code calls Set-Content at all + + .EXAMPLE + Mock Set-Content {} + + {... Some Code ...} + + Should-NotInvoke Set-Content -Exactly 2 + + This will throw an exception if some code does not call Set-Content Exactly two times. + + .EXAMPLE + Describe 'Should-NotInvoke Scope behavior' { + Mock Set-Content { } + + It 'Calls Set-Content at least once in the It block' { + {... Some Code ...} + + Should-NotInvoke Set-Content -Exactly 0 -Scope It + } + } + + Checks for calls only within the current It block. + + .EXAMPLE + Describe 'Describe' { + Mock -ModuleName SomeModule Set-Content { } + + {... Some Code ...} + + It 'Calls Set-Content at least once in the Describe block' { + Should-NotInvoke -ModuleName SomeModule Set-Content + } + } + + Checks for calls to the mock within the SomeModule module. Note that both the Mock + and Should-NotInvoke commands use the same module name. + + .EXAMPLE + Should-NotInvoke Get-ChildItem -ExclusiveFilter { $Path -eq 'C:\' } + + Checks to make sure that Get-ChildItem was called at least one time with + the -Path parameter set to 'C:\', and that it was not called at all with + the -Path parameter set to any other value. + + .NOTES + The parameter filter passed to Should-NotInvoke does not necessarily have to match the parameter filter + (if any) which was used to create the Mock. Should-NotInvoke will find any entry in the command history + which matches its parameter filter, regardless of how the Mock was created. However, if any calls to the + mocked command are made which did not match any mock's parameter filter (resulting in the original command + being executed instead of a mock), these calls to the original command are not tracked in the call history. + In other words, Should-NotInvoke can only be used to check for calls to the mocked implementation, not + to the original. + + .LINK + https://pester.dev/docs/commands/Should-NotInvoke + + .LINK + https://pester.dev/docs/assertions + #> + [CmdletBinding(DefaultParameterSetName = 'Default')] + param( + [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'Default')] + [string]$CommandName, + + [Parameter(Position = 1, ParameterSetName = 'Default')] + [int]$Times = 1, + + [parameter(ParameterSetName = 'Default')] + [ScriptBlock]$ParameterFilter = { $True }, + + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'ExclusiveFilter', Mandatory = $true)] + [scriptblock] $ExclusiveFilter, + + [Parameter(ParameterSetName = 'Default')] + [string] $ModuleName, + [Parameter(ParameterSetName = 'Default')] + [string] $Scope = 0, + [Parameter(ParameterSetName = 'Default')] + [switch] $Exactly, + [Parameter(ParameterSetName = 'Default')] + [Parameter(ParameterSetName = 'Verifiable')] + [string] $Because, + + [Parameter(ParameterSetName = 'Verifiable')] + [switch] $Verifiable + ) + + $PSBoundParameters["Negate"] = $true + + if ($PSBoundParameters.ContainsKey('Verifiable')) { + $PSBoundParameters.Remove('Verifiable') + $testResult = Should-InvokeVerifiable @PSBoundParameters + Test-AssertionResult $testResult + return + } + + # Maps the parameters so we can internally use functions that is + # possible to register as Should operator. + $PSBoundParameters["ActualValue"] = $null + $PSBoundParameters["CallerSessionState"] = $PSCmdlet.SessionState + $testResult = Should-InvokeAssertion @PSBoundParameters + + Test-AssertionResult $testResult +} diff --git a/src/functions/assertions/Should.ps1 b/src/functions/assertions/Should.ps1 index fc0130d4d..785fc92b5 100644 --- a/src/functions/assertions/Should.ps1 +++ b/src/functions/assertions/Should.ps1 @@ -238,8 +238,16 @@ function Invoke-Assertion { $testResult = & $AssertionEntry.Test -ActualValue $ValueToTest -Negate:$Negate -CallerSessionState $CallerSessionState @BoundParameters - if (-not $testResult.Succeeded) { - $errorRecord = [Pester.Factory]::CreateShouldErrorRecord($testResult.FailureMessage, $file, $lineNumber, $lineText, $shouldThrow, $testResult) + Test-AssertionResult $testResult +} + +function Test-AssertionResult { + param ( + $TestResult + ) + + if (-not $TestResult.Succeeded) { + $errorRecord = [Pester.Factory]::CreateShouldErrorRecord($TestResult.FailureMessage, $file, $lineNumber, $lineText, $shouldThrow, $TestResult) if ($null -eq $AddErrorCallback -or $ShouldThrow) { # throw this error to fail the test immediately @@ -261,11 +269,12 @@ function Invoke-Assertion { } else { #extract data to return if there are any on the object - $data = $testResult.psObject.Properties.Item('Data') + $data = $TestResult.psObject.Properties.Item('Data') if ($data) { $data.Value } } + } function Format-Because ([string] $Because) { diff --git a/tst/Pester.RSpec.TestResults.JUnit4.ts.ps1 b/tst/Pester.RSpec.TestResults.JUnit4.ts.ps1 index 1f3a3d27e..6b15bfff7 100644 --- a/tst/Pester.RSpec.TestResults.JUnit4.ts.ps1 +++ b/tst/Pester.RSpec.TestResults.JUnit4.ts.ps1 @@ -70,7 +70,6 @@ i -PassThru:$PassThru { $failureLine = $sb.StartPosition.StartLine + 3 $stackTraceText = @($xmlTestCase.failure.'#text' -split "`n" -replace "`r") $stackTraceText[0] | Verify-Equal "at ""Testing"" | Should -Be ""Test"", ${PSCommandPath}:$failureLine" - $stackTraceText[1] | Verify-Equal "at , ${PSCommandPath}:$failureLine" } t "should write skipped and filtered test results counts" { @@ -138,9 +137,7 @@ i -PassThru:$PassThru { $failureLine = $sb.StartPosition.StartLine + 3 $stackTraceText = @($xmlTestCase.failure.'#text' -split "`n" -replace "`r") $stackTraceText[0] | Verify-Equal "[0] at ""Testing"" | Should -Be ""Test"", ${PSCommandPath}:$failureLine" - $stackTraceText[1] | Verify-Equal "at , ${PSCommandPath}:$($sbStartLine+3)" - $stackTraceText[2] | Verify-Equal "[1] at , ${PSCommandPath}:$($sbStartLine+7)" - + $stackTraceText[1] | Verify-Equal "[1] at , ${PSCommandPath}:$($sbStartLine+7)" } t "should use expanded path and name when there are any" { diff --git a/tst/Pester.RSpec.TestResults.NUnit25.ts.ps1 b/tst/Pester.RSpec.TestResults.NUnit25.ts.ps1 index 9f9c04f8d..97083089a 100644 --- a/tst/Pester.RSpec.TestResults.NUnit25.ts.ps1 +++ b/tst/Pester.RSpec.TestResults.NUnit25.ts.ps1 @@ -65,7 +65,6 @@ i -PassThru:$PassThru { $failureLine = $sb.StartPosition.StartLine + 3 $stackTraceText = $xmlTestCase.failure.'stack-trace' -split "`n" $stackTraceText[0] | Verify-Equal "at ""Testing"" | Should -Be ""Test"", ${PSCommandPath}:$failureLine" - $stackTraceText[1] | Verify-Equal "at , ${PSCommandPath}:$failureLine" } t "should write a failed test result when there are multiple errors" { @@ -102,9 +101,7 @@ i -PassThru:$PassThru { $failureLine = $sb.StartPosition.StartLine + 3 $stackTraceText = $xmlTestCase.failure.'stack-trace' -split "`n" $stackTraceText[0] | Verify-Equal "[0] at ""Testing"" | Should -Be ""Test"", ${PSCommandPath}:$failureLine" - $stackTraceText[1] | Verify-Equal "at , ${PSCommandPath}:$($sbStartLine+3)" - $stackTraceText[2] | Verify-Equal "[1] at , ${PSCommandPath}:$($sbStartLine+7)" - + $stackTraceText[1] | Verify-Equal "[1] at , ${PSCommandPath}:$($sbStartLine+7)" } t "should write a skipped test result" { diff --git a/tst/Pester.RSpec.TestResults.NUnit3.ts.ps1 b/tst/Pester.RSpec.TestResults.NUnit3.ts.ps1 index d000a4e4e..cd16755ec 100644 --- a/tst/Pester.RSpec.TestResults.NUnit3.ts.ps1 +++ b/tst/Pester.RSpec.TestResults.NUnit3.ts.ps1 @@ -68,7 +68,6 @@ i -PassThru:$PassThru { $failureLine = $sb.StartPosition.StartLine + 3 $stackTraceText = $xmlTestCase.failure.'stack-trace'.'#cdata-section' -split "`n" $stackTraceText[0] | Verify-Equal "at 'Testing' | Should -Be 'Test', ${PSCommandPath}:$failureLine" - $stackTraceText[1] | Verify-Equal "at , ${PSCommandPath}:$failureLine" } t 'should write a failed test result when there are multiple errors' { @@ -105,9 +104,7 @@ i -PassThru:$PassThru { $failureLine = $sb.StartPosition.StartLine + 3 $stackTraceText = $xmlTestCase.failure.'stack-trace'.'#cdata-section' -split "`n" $stackTraceText[0] | Verify-Equal "[0] at 'Testing' | Should -Be 'Test', ${PSCommandPath}:$failureLine" - $stackTraceText[1] | Verify-Equal "at , ${PSCommandPath}:$($sbStartLine+3)" - $stackTraceText[2] | Verify-Equal "[1] at , ${PSCommandPath}:$($sbStartLine+7)" - + $stackTraceText[1] | Verify-Equal "[1] at , ${PSCommandPath}:$($sbStartLine+7)" } t 'should write a skipped test result' { diff --git a/tst/functions/Output.Tests.ps1 b/tst/functions/Output.Tests.ps1 index 3b54bedc3..ef72ac2e6 100644 --- a/tst/functions/Output.Tests.ps1 +++ b/tst/functions/Output.Tests.ps1 @@ -186,8 +186,7 @@ InModuleScope -ModuleName Pester -ScriptBlock { $r.Message.Count | Should -be 6 $r.Trace[0] | Should -match "'One' | Should -be 'Two'" - $r.Trace[1] | Should -be "at , ${PSCommandPath}:172" - $r.Trace.Count | Should -be 2 + $r.Trace.Count | Should -be 1 } # TODO: should fails with a very weird error, probably has something to do with dynamic params... # Context 'Should fails in file' { @@ -265,7 +264,7 @@ InModuleScope -ModuleName Pester -ScriptBlock { $r.Trace[0] | Should -be "at f1, ${testPath}:2" $r.Trace[1] | Should -be "at f2, ${testPath}:5" $r.Trace[2] | Should -be "at , ${testPath}:7" - $r.Trace[3] | Should -be "at , ${PSCommandPath}:248" + $r.Trace[3] | Should -be "at , ${PSCommandPath}:247" $r.Trace.Count | Should -be 4 } } @@ -276,7 +275,7 @@ InModuleScope -ModuleName Pester -ScriptBlock { $r.Trace[0] | Should -be "at f1, ${testPath}:2" $r.Trace[1] | Should -be "at f2, ${testPath}:5" $r.Trace[2] | Should -be "at , ${testPath}:7" - $r.Trace[3] | Should -be "at , ${PSCommandPath}:248" + $r.Trace[3] | Should -be "at , ${PSCommandPath}:247" $r.Trace.Count | Should -be 4 } } @@ -337,7 +336,7 @@ InModuleScope -ModuleName Pester -ScriptBlock { It 'produces correct trace line.' { if ($hasStackTrace) { $r.Trace[0] | Should -be "at , $testPath`:10" - $r.Trace[1] | Should -be "at , $PSCommandPath`:314" + $r.Trace[1] | Should -be "at , $PSCommandPath`:313" $r.Trace.Count | Should -be 2 } } @@ -346,7 +345,7 @@ InModuleScope -ModuleName Pester -ScriptBlock { It 'produces correct trace line.' { if ($hasStackTrace) { $r.Trace[0] | Should -be "at , $testPath`:10" - $r.Trace[1] | Should -be "at , $PSCommandPath`:314" + $r.Trace[1] | Should -be "at , $PSCommandPath`:313" $r.Trace.Count | Should -be 2 } } @@ -435,7 +434,7 @@ InModuleScope -ModuleName Pester -ScriptBlock { $errorMessage = Format-ErrorMessage -Err $errorRecord -StackTraceVerbosity $_ $messages = $errorMessage -split [Environment]::NewLine $messages[0] | Should -BeExactly "System.DivideByZeroException: Attempted to divide by zero." - $messages[1] | Should -BeExactly "at , ${PSCommandPath}: line 389" + $messages[1] | Should -BeExactly "at , ${PSCommandPath}: line 388" $messages.Count | Should -BeGreaterThan 1 } diff --git a/tst/functions/assert/Mock/Should-Invoke.Tests.ps1 b/tst/functions/assert/Mock/Should-Invoke.Tests.ps1 new file mode 100644 index 000000000..1abd620ba --- /dev/null +++ b/tst/functions/assert/Mock/Should-Invoke.Tests.ps1 @@ -0,0 +1,37 @@ +Set-StrictMode -Version Latest + +Describe "Should-Invoke" { + It "Passes when Mock was invoked once" { + function f () { } + Mock f + + f + + Should-Invoke f -Times 1 -Exactly + } + + It "Failes when mock was invoked 0 times" { + function f () { } + Mock f + + { Should-Invoke f -Times 1 -Exactly } | Verify-Throw + } +} + +Describe "Should-Invoke -Verifiable" { + It "Passes when all verifiable mocks were invoked" { + function f () { } + Mock f -Verifiable + + f + + Should-Invoke -Verifiable + } + + It "Fails when not all verifiable mocks were invoked" { + function f () { } + Mock f -Verifiable + + { Should-Invoke -Verifiable } | Verify-Throw + } +} diff --git a/tst/functions/assert/Mock/Should-NotInvoke.Tests.ps1 b/tst/functions/assert/Mock/Should-NotInvoke.Tests.ps1 new file mode 100644 index 000000000..9b00131f3 --- /dev/null +++ b/tst/functions/assert/Mock/Should-NotInvoke.Tests.ps1 @@ -0,0 +1,37 @@ +Set-StrictMode -Version Latest + +Describe "Should-NotInvoke" { + It "Passes when Mock was not invoked" { + function f () { } + Mock f + + Should-NotInvoke f -Times 1 -Exactly + } + + It "Fails when Mock was invoked" { + function f () { } + Mock f + + f + + { Should-NotInvoke f -Times 1 -Exactly } | Verify-Throw + } +} + +Describe "Should-NotInvoke -Verifiable" { + It "Passes when no verifiable mocks were invoked" { + function f () { } + Mock f -Verifiable + + Should-NotInvoke -Verifiable + } + + It "Fails when verifiable mocks were invoked" { + function f () { } + Mock f -Verifiable + + f + + { Should-NotInvoke -Verifiable } | Verify-Throw + } +}