diff --git a/Functions/Mock.Tests.ps1 b/Functions/Mock.Tests.ps1 index 1097a5132..7f2edd6bc 100644 --- a/Functions/Mock.Tests.ps1 +++ b/Functions/Mock.Tests.ps1 @@ -1516,3 +1516,20 @@ Describe 'Mocking Get-Command' { { Mock Get-Command } | Should Not Throw } } + +Describe 'Mocks with closures' { + $closureVariable = 'from closure' + $scriptBlock = { "Variable resolved $closureVariable" } + $closure = $scriptBlock.GetNewClosure() + $closureVariable = 'from script' + + function TestClosure([switch] $Closure) { 'Not mocked' } + + Mock TestClosure $closure -ParameterFilter { $Closure } + Mock TestClosure $scriptBlock + + It 'Resolves variables in the closure rather than Pester''s current scope' { + TestClosure | Should Be 'Variable resolved from script' + TestClosure -Closure | Should Be 'Variable resolved from closure' + } +} diff --git a/Functions/Mock.ps1 b/Functions/Mock.ps1 index e53517d5a..a56e4bc28 100644 --- a/Functions/Mock.ps1 +++ b/Functions/Mock.ps1 @@ -177,8 +177,17 @@ about_Mocking $ModuleName = '' } - $mockWithCopy = [scriptblock]::Create($MockWith.ToString()) - Set-ScriptBlockScope -ScriptBlock $mockWithCopy -SessionState $contextInfo.Session + if (Test-IsClosure -ScriptBlock $MockWith) + { + # If the user went out of their way to call GetNewClosure(), go ahead and leave the block bound to that + # dynamic module's scope. + $mockWithCopy = $MockWith + } + else + { + $mockWithCopy = [scriptblock]::Create($MockWith.ToString()) + Set-ScriptBlockScope -ScriptBlock $mockWithCopy -SessionState $contextInfo.Session + } $block = @{ Mock = $mockWithCopy @@ -1350,3 +1359,24 @@ function Get-DynamicParametersForMockedFunction return & $mock.DynamicParamScriptBlock @Parameters @splat } } + +function Test-IsClosure +{ + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [scriptblock] + $ScriptBlock + ) + + $sessionStateInternal = Get-ScriptBlockScope -ScriptBlock $ScriptBlock + + $flags = [System.Reflection.BindingFlags]'Instance,NonPublic' + $module = $sessionStateInternal.GetType().GetProperty('Module', $flags).GetValue($sessionStateInternal, $null) + + return ( + $null -ne $module -and + $module.Name -match '^__DynamicModule_([a-f\d-]+)$' -and + $null -ne ($matches[1] -as [guid]) + ) +}