Skip to content

Commit 20b9cdd

Browse files
committed
Mock fallthrough
1 parent d08debe commit 20b9cdd

File tree

3 files changed

+37
-78
lines changed

3 files changed

+37
-78
lines changed

src/functions/Mock.ps1

Lines changed: 31 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ function Should-InvokeVerifiableInternal {
348348
}
349349

350350
return [Pester.ShouldResult] @{
351-
Succeeded = $true
351+
Succeeded = $true
352352
}
353353
}
354354

@@ -460,7 +460,8 @@ function Should-InvokeInternal {
460460
# $params.ScriptBlock = New-BlockWithoutParameterAliases -Metadata $ContextInfo.Hook.Metadata -Block $params.ScriptBlock
461461
# }
462462

463-
if (Test-ParameterFilter @params) {
463+
$passed, $filterInvocations = Test-ParameterFilter @params
464+
if ($passed) {
464465
$null = $matchingCalls.Add($historyEntry)
465466
}
466467
else {
@@ -530,7 +531,7 @@ function Should-InvokeInternal {
530531
}
531532

532533
return [Pester.ShouldResult] @{
533-
Succeeded = $true
534+
Succeeded = $true
534535
}
535536
}
536537

@@ -798,7 +799,7 @@ function Invoke-MockInternal {
798799
switch ($FromBlock) {
799800
Begin {
800801
$MockCallState['InputObjects'] = [System.Collections.Generic.List[object]]@()
801-
$MockCallState['ShouldExecuteOriginalCommand'] = $false
802+
$MockCallState['MatchedNoBehavior'] = $false
802803
$MockCallState['BeginBoundParameters'] = $BoundParameters.Clone()
803804
# argument list must not be null, if the bootstrap functions has no parameters
804805
# we get null and need to replace it with empty array to make the splatting work
@@ -816,7 +817,7 @@ function Invoke-MockInternal {
816817
$SessionState = if ($CallerSessionState) { $CallerSessionState } else { $Hook.SessionState }
817818

818819
# the @() are needed for powerShell3 otherwise it throws CheckAutomationNullInCommandArgumentArray (unless there is any breakpoint defined anywhere, then it works just fine :DDD)
819-
$behavior = FindMatchingBehavior -Behaviors @($Behaviors) -BoundParameters $BoundParameters -ArgumentList @($ArgumentList) -SessionState $SessionState -Hook $Hook
820+
$behavior, $failedFilterInvocations = FindMatchingBehavior -Behaviors @($Behaviors) -BoundParameters $BoundParameters -ArgumentList @($ArgumentList) -SessionState $SessionState -Hook $Hook
820821

821822
if ($null -ne $behavior) {
822823
$call = @{
@@ -841,7 +842,8 @@ function Invoke-MockInternal {
841842
return
842843
}
843844
else {
844-
$MockCallState['ShouldExecuteOriginalCommand'] = $true
845+
$MockCallState['MatchedNoBehavior'] = $true
846+
$MockCallState['FailedFilterInvocations'] = $failedFilterInvocations
845847
if ($null -ne $InputObject) {
846848
$null = $MockCallState['InputObjects'].AddRange(@($InputObject))
847849
}
@@ -851,69 +853,14 @@ function Invoke-MockInternal {
851853
}
852854

853855
End {
854-
if ($MockCallState['ShouldExecuteOriginalCommand']) {
856+
if ($MockCallState['MatchedNoBehavior']) {
855857
if ($PesterPreference.Debug.WriteDebugMessages.Value) {
856-
Write-PesterDebugMessage -Scope Mock "Invoking the original command."
858+
Write-PesterDebugMessage -Scope Mock "The mock did not match any filtered behavior, and there was no default behavior. Failing."
857859
}
858860

859-
$MockCallState['BeginBoundParameters'] = Reset-ConflictingParameters -BoundParameters $MockCallState['BeginBoundParameters']
861+
$failedFilterInvocations = $MockCallState['FailedFilterInvocations']
860862

861-
if ($MockCallState['InputObjects'].Count -gt 0) {
862-
$scriptBlock = {
863-
param ($Command, $ArgumentList, $BoundParameters, $InputObjects)
864-
$InputObjects | & $Command @ArgumentList @BoundParameters
865-
}
866-
}
867-
else {
868-
$scriptBlock = {
869-
param ($Command, $ArgumentList, $BoundParameters, $InputObjects)
870-
& $Command @ArgumentList @BoundParameters
871-
}
872-
}
873-
874-
$SessionState = if ($CallerSessionState) {
875-
$CallerSessionState
876-
}
877-
else {
878-
$Hook.SessionState
879-
}
880-
881-
Set-ScriptBlockScope -ScriptBlock $scriptBlock -SessionState $SessionState
882-
883-
# In order to mock Set-Variable correctly we need to write the variable
884-
# two scopes above
885-
if ("Set-Variable" -eq $Hook.OriginalCommand.Name) {
886-
if ($PesterPreference.Debug.WriteDebugMessages.Value) {
887-
Write-PesterDebugMessage -Scope Mock "Original command is Set-Variable, patching the call."
888-
}
889-
if ($MockCallState['BeginBoundParameters'].Keys -notcontains "Scope") {
890-
$MockCallState['BeginBoundParameters'].Add( "Scope", 2)
891-
}
892-
# local is the same as scope 0, in that case we also write to scope 2
893-
elseif ("Local", "0" -contains $MockCallState['BeginBoundParameters'].Scope) {
894-
$MockCallState['BeginBoundParameters'].Scope = 2
895-
}
896-
elseif ($MockCallState['BeginBoundParameters'].Scope -match "\d+") {
897-
$MockCallState['BeginBoundParameters'].Scope = 2 + $matches[0]
898-
}
899-
else {
900-
# not sure what the user did, but we won't change it
901-
}
902-
}
903-
904-
if ($null -eq ($MockCallState['BeginArgumentList'])) {
905-
$arguments = @()
906-
}
907-
else {
908-
$arguments = $MockCallState['BeginArgumentList']
909-
}
910-
if ($PesterPreference.Debug.WriteDebugMessages.Value) {
911-
Write-ScriptBlockInvocationHint -Hint "Mock - Original Command" -ScriptBlock $scriptBlock
912-
}
913-
& $scriptBlock -Command $Hook.OriginalCommand `
914-
-ArgumentList $arguments `
915-
-BoundParameters $MockCallState['BeginBoundParameters'] `
916-
-InputObjects $MockCallState['InputObjects']
863+
throw "The mock for command '$($Hook.CommandName)' did not match any filtered behavior, and there was no default behavior.`nPerformed ParameterFilter evaluations:`n$($failedFilterInvocations -join '`n')`n"
917864
}
918865
}
919866
}
@@ -980,6 +927,7 @@ function FindMatchingBehavior {
980927
Write-PesterDebugMessage -Scope Mock "Finding behavior to use, one that passes filter or a default:"
981928
}
982929

930+
$failedFilterInvocations = [System.Collections.Generic.List[String]]@()
983931
$foundDefaultBehavior = $false
984932
$defaultBehavior = $null
985933
foreach ($b in $Behaviors) {
@@ -999,11 +947,15 @@ function FindMatchingBehavior {
999947
SessionState = $Hook.CallerSessionState
1000948
}
1001949

1002-
if (Test-ParameterFilter @params) {
950+
$passed, $filterInvocations = Test-ParameterFilter @params
951+
if ($passed) {
1003952
if ($PesterPreference.Debug.WriteDebugMessages.Value) {
1004953
Write-PesterDebugMessage -Scope Mock "{ $($b.ScriptBlock) } passed parameter filter and will be used for the mock call."
1005954
}
1006-
return $b
955+
return $b, $null
956+
}
957+
else {
958+
$failedFilterInvocations.AddRange($filterInvocations)
1007959
}
1008960
}
1009961
}
@@ -1012,13 +964,13 @@ function FindMatchingBehavior {
1012964
if ($PesterPreference.Debug.WriteDebugMessages.Value) {
1013965
Write-PesterDebugMessage -Scope Mock "{ $($defaultBehavior.ScriptBlock) } is a default behavior and will be used for the mock call."
1014966
}
1015-
return $defaultBehavior
967+
return $defaultBehavior, $null
1016968
}
1017969

1018970
if ($PesterPreference.Debug.WriteDebugMessages.Value) {
1019-
Write-PesterDebugMessage -Scope Mock "No parametrized or default behaviors were found filter."
971+
Write-PesterDebugMessage -Scope Mock "No parametrized or default behaviors were found."
1020972
}
1021-
return $null
973+
return $null, $failedFilterInvocations
1022974
}
1023975

1024976
function LastThat {
@@ -1241,6 +1193,8 @@ function Test-ParameterFilter {
12411193
else { $null }
12421194
}
12431195

1196+
$parameterFilterInvocations = [Collections.Generic.List[string]]@()
1197+
12441198
$result = & $wrapper $parameters
12451199
if ($result) {
12461200
if ($PesterPreference.Debug.WriteDebugMessages.Value) {
@@ -1251,8 +1205,15 @@ function Test-ParameterFilter {
12511205
if ($PesterPreference.Debug.WriteDebugMessages.Value) {
12521206
Write-PesterDebugMessage -Scope Mock -Message "Mock filter returned value '$result', which is falsy. Filter did not pass."
12531207
}
1208+
1209+
# Filter did not pass, serialize the values and store them in them for future reference in case we don't find any behavior.
1210+
$hasContext = 0 -lt $Context.Count
1211+
$c = $(if ($hasContext) { foreach ($p in $Context.GetEnumerator()) { "$($p.Key) = $($p.Value)" } }) -join ", "
1212+
$filterCall = "mock filter: { $scriptBlock } $(if ($hasContext) { "with parameters: $c" } else { "without any parameters"})"
1213+
$parameterFilterInvocations.Add($filterCall)
12541214
}
12551215
$result
1216+
, $parameterFilterInvocations
12561217
}
12571218

12581219
function Get-ContextToDefine {

src/functions/Pester.SessionState.Mock.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,9 +1001,9 @@ function Invoke-Mock {
10011001
)
10021002

10031003
if ('End' -eq $FromBlock) {
1004-
if (-not $MockCallState.ShouldExecuteOriginalCommand) {
1004+
if (-not $MockCallState.MatchedNoBehavior) {
10051005
if ($PesterPreference.Debug.WriteDebugMessages.Value) {
1006-
Write-PesterDebugMessage -Scope MockCore "Mock for $CommandName was invoked from block $FromBlock, and should not execute the original command, returning."
1006+
Write-PesterDebugMessage -Scope MockCore "Mock for $CommandName was invoked from block $FromBlock, and matched at least one behavior, returning."
10071007
}
10081008
return
10091009
}

tst/functions/Mock.Tests.ps1

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -373,10 +373,9 @@ Describe 'When calling Mock, StrictMode is enabled, and variables are used in th
373373
}
374374

375375
Describe "When calling Mock on existing function without matching bound params" {
376-
It "Should redirect to real function" {
376+
It "Should throw because not mock matched the invocation" {
377377
Mock FunctionUnderTest { return "fake results" } -parameterFilter { $param1 -eq "test" }
378-
$result = FunctionUnderTest "badTest"
379-
$result | Should -Be "I am a real world test"
378+
{ FunctionUnderTest "badTest" } | Should -Throw "Mock felt though"
380379
}
381380
}
382381

@@ -389,10 +388,9 @@ Describe "When calling Mock on existing function with matching bound params" {
389388
}
390389

391390
Describe "When calling Mock on existing function without matching unbound arguments" {
392-
It "Should redirect to real function" {
391+
It "Should throw because not mock matched the invocation" {
393392
Mock FunctionUnderTestWithoutParams { return "fake results" } -parameterFilter { $param1 -eq "test" -and $args[0] -eq 'notArg0' }
394-
$result = FunctionUnderTestWithoutParams -param1 "test" "arg0"
395-
$result | Should -Be "I am a real world test with no params"
393+
{ FunctionUnderTestWithoutParams -param1 "test" "arg0" } | Should -Throw "Mock felt though"
396394
}
397395
}
398396

0 commit comments

Comments
 (0)