New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Question: Is it possible to "parameterize" the ParameterFilter for Mocks? #1162
Comments
@fourpastmidnight I don't understand what you are after. Coould you simplify you example to show me only the relevant stuff in a simple code? Here is a simple example of what I think you want, but I cannot break it. Once we can replicated it in simple example I can find out what is possible. function a ($Parameter) {}
Describe "a" {
It "b" {
$table = "1"
Mock a -ParameterFilter {
# adding the param throws because Pester generates one for you
# so it's duplicated
# param($Parameter)
Write-Host "Parameter -$Parameter-"
Write-Host "Table -$table-"
# you want $table to be 1?
$Parameter -eq $table
}
a -Parameter 10
}
} |
@nohwnd Simplistically, that is what I want, but with a Gherkin test. I just filed a bug with respect to Gherkin tests and setting the scope when calling Then "this should pass" {
param([hashtable[]]$Table)
Mock Invoke-vRARestMethod -ModuleName 'DevOps.DecommissionServer'
for ($n = 0; $n -lt $Table.Length; $n++) {
$params = $Script:Context.GetVraResourceParameters[$n]
Get-vRAResource -vRASession $vRASession @params
Assert-MockCalled Invoke-vRARestMethod -ModuleName 'DevOps.DecommissionServer' -ParameterFilter { "$ApiUriSegment" -ieq "$($Table[$n].UrlSubstring)" } #-Scope Scenario #<-- Should I need this??
}
} But whenever I step through the dynamically created script block for the parameter filter (in |
Sorry a bit of a follow-on: Now, because the scopes did not seem to be set properly (at the beginning, I wasn't really sure what's going on), I started asking myself whether I needed to use closures to effectively capture the "Local" variable for the parameter filter, which lead to some of the crazy code up there. I was hoping that by using a closure, it would force the local variable to be "in scope": $Script:Context.ParameterFilterClosure = {
param([Url]$ExpectedApiUriSegment)
$expected = $ExpectedApiUriSegment
{
"$ApiUriSegment" -ieq "$expected"
}.GetNewClosure()
}
# ...
Mock Invoke-vRARestApi -ModuleName 'DevOps.DecommissionServer'
for ($n = 0; $n -lt $Table.Length; $n++) {
$parameterFilter = &$Script:Context.ParameterFilterCloser [Uri]::new($Table[$n}.UrlSubstring, [UriKind]::Relative)
$params = $Script:Context.GetvRAResourceParameters[$n]
Get-vRAResource -vRASession $Script:Context.vRASession @params
# This still doesn't work because the scope of $parameterFilter won't have $expected....
# What gives? I'm still learning about PowerShell scoping, but my understanding is that this _should_ work!
Assert-MockCall Invoke-vRARestMethod -ModuleName 'DevOps.DecommissionServer' -ParameterFilter $parameterFilter
} |
@nohwnd OK, so the above is not an MVCE (minimally viable and complete example). Let me see if I can come up with one which will demonstrate the problem. That should've been my first go-to in "Triaging" this problem anyway... 😉 |
Does |
@nohwnd No, it has many parameters, but all but one are optional. And the test above is only attempting to test 3 of the optional parameters. Stepping through the code in the debugger, when we get to the dynamically generated script block being executed by This whole issue is around the fact that:
It's really strange, but the scopes just don't seem to be properly set up/attached to the generated script block. I'm working on creating a very minimal module with only the most relevant code and the tests above. I'll hopefully have something pushed up to my GitHub account later today. When I do, I'll post a link here. |
@nohwnd OK, I just created a repo in my GitHub account which, as minimally as possible, attempts to:
If you have a bit of time to look at it, I would be very appreciative. |
One more note: there's a file in the repo called |
@fourpastmidnight This was a nice problem to debug, I actually wrote a lot of super useful code to figure it out :D Here is output from Pester with scope related instrumentation: PS C:\Projects\PesterGherkinMockScopeTests> cd C:\Projects\PesterGherkinMockScopeTests
#get-item variable:ExpectedUriApiSegment | Remove-Item -force
Get-Module Pester | Remove-Module
Import-Module C:\Projects\pester_main\Pester.psm1
Get-Module PesterGherkinMockScopeTests | remove-module
$ExpectedUriApiSegment = "Hello"
Invoke-Gherkin -Path ./specs/features -ExcludeTag ignore -PesterOption (New-PesterOption -TestSuiteName 'PesterGherkinMockSopeTests' -IncludeVSCodeMarker)
Testing all features in './specs/features'
Setting ScriptBlock state from source state 'Pester (42654543))' to 'Caller - Captured in Invoke-Gherkin (61485005))'
Setting ScriptBlock state from source state 'Pester (42654543))' to 'Caller - Captured in Invoke-Gherkin (61485005))'
Setting ScriptBlock state from source state 'Pester (42654543))' to 'Caller - Captured in Invoke-Gherkin (61485005))'
Setting ScriptBlock state from source state 'Pester (42654543))' to 'Caller - Captured in Invoke-Gherkin (61485005))'
Feature: Can query for resources from vRA
As a consumer of the vRA REST API
I want to be able to retrieve information about resources
So that I can programmatically manage vRA resources
Scenario: Can generate correct URLs with specified query options
Setting ScriptBlock state from source state 'Caller - Captured in Invoke-Gherkin (61485005))' to 'Scenario (53986122))'
Invoking scriptblock from location 'Invoke-Gherkin step' in state 'Scenario (53986122))', 0 scopes deep:
{
if (!$Script:Context) {
$Script:Context = @{}
}
$Script:Context.vRAServerFQDN = 'vra.mycompany.com'
$Script:Context.Credential = [PSCredential]::new('a-user', ('a-password' | ConvertTo-SecureString -AsPlainText -Force))
$Script:Context.Tenant = 'the-tenant'
$step = Find-GherkinStep -Step "When a new vRA session is requested via the 'Credential' parameter set"
Invoke-Command $step.Implementation -ArgumentList 'Credential'
$vRASession = $Script:Context.Actual
$Script:Context = @{}
$Script:Context.vRASession = $vRASession
}
Setting ScriptBlock state from source state 'Pester (42654543))' to 'Scenario (53986122))'
Setting ScriptBlock state from source state 'Pester (42654543))' to 'Scenario (53986122))'
Setting ScriptBlock state from source state 'Pester (42654543))' to 'Scenario (53986122))'
Setting ScriptBlock state from source state 'Pester (42654543))' to 'Scenario (53986122))'
Getting scope from ScriptBlock 'Scenario (53986122))'
Unbinding ScriptBlock from 'Scenario (53986122))'
Setting ScriptBlock state from source state 'Unbound ScriptBlock from Mock (Unbound)' to 'Module - PesterGherkinMockScopeTests (10265017))'
Unbinding ScriptBlock from 'Scenario (53986122))'
Setting ScriptBlock state from source state 'Unbound ScriptBlock from Test-ParameterFilter (Unbound)' to 'Caller - Captured in Invoke-Gherkin (61485005))'
Invoking scriptblock from location 'Mock - Parameter filter' in state 'Caller - Captured in Invoke-Gherkin (61485005))', 0 scopes deep:
{
[CmdletBinding(HelpUri='https://go.microsoft.com/fwlink/?LinkID=217034')] param (${Body},${Headers},${Method},${ContentType},${Uri})
Set-StrictMode -Off
$Body -and ($Body | ConvertFrom-JSON).tenant -eq 'the-tenant'
}
Setting ScriptBlock state from source state 'Pester (42654543))' to 'Module - PesterGherkinMockScopeTests (10265017))'
Invoking scriptblock from location 'Mock - of command Invoke-RestMethodfrom module PesterGherkinMockScopeTests' in state 'Module - PesterGherkinMockScopeTests (10265017))', 2 scopes deep:
{
@{
expires = $Global:expiry
id = 'the-token'
tenant = 'the-tenant'
} | ConvertTo-JSON
}
Invoking scriptblock from location 'Mock - of command Invoke-RestMethod' in state 'Module - PesterGherkinMockScopeTests (10265017))', 3 scopes deep:
{
@{
expires = $Global:expiry
id = 'the-token'
tenant = 'the-tenant'
} | ConvertTo-JSON
}
[+] Given an authorized REST API user has authenticated with vRA 224ms
Setting ScriptBlock state from source state 'Scenario (53986122))' to 'Scenario (53986122))'
Invoking scriptblock from location 'Invoke-Gherkin step' in state 'Scenario (53986122))', 0 scopes deep:
{
param([hashtable[]]$Table)
# $Script:Context is being used similarly to 'world' in Ruby's cucumber.
if (!$Script:Context) {
$Script:Context = @{}
}
$Script:Context.GetVraResourceParameters = @(foreach ($row in $Table) {
$params = @{
ManagedOnly = (ConvertFrom-TableCellValue $row.ManagedOnly) -as [switch]
WithExtendedData = (ConvertFrom-TableCellValue $row.WithExtendedData) -as [switch]
WithOperations = (ConvertFrom-TableCellValue $row.WithOperations) -as [switch]
}
foreach ($key in $params.Keys.Clone()) {
if ($null -eq $params[$key]) {
$params.Remove($key)
}
}
$params
})
}
[+] Given the following query options:... 8ms
Setting ScriptBlock state from source state 'Scenario (53986122))' to 'Scenario (53986122))'
Invoking scriptblock from location 'Invoke-Gherkin step' in state 'Scenario (53986122))', 0 scopes deep:
{
$Script:Context.ParameterFilterClosure = {
param ([Uri]$ExpectedApiUriSegment)
# I probably don't need to do this, but following an example at
# https://blogs.technet.microsoft.com/heyscriptingguy/2013/04/05/closures-in-powershell/
$_ExpectedApiUriSegment = $ExpectedApiUriSegment
{
if ("$ApiUriSegment" -ieq "$_ExpectedApiUriSegment") {
$true
} else {
Write-Host "This mock will not be executed because the 'ApiUriSegment' parameter did not have the expected value:"
Write-Host " Expected: $_ExpectedApiUriSegment"
Write-Host " Actual: $ApiUriSegment"
$false
}
}.GetNewClosure()
}
}
[+] When Get-vRAResource is invoked with the given parameters 39ms
Setting ScriptBlock state from source state 'Scenario (53986122))' to 'Scenario (53986122))'
+ Invoking scriptblock from location 'Invoke-Gherkin step' in state 'Scenario (53986122))', 0 scopes deep:
{
param([hashtable[]]$Table)
# Invoke-vRARestMethod is called from Get-vRAResource, so need to use -ModuleName parameter
# since Invoke-vRARestMethod is invoked within the context of the module itself.
Mock Invoke-vRARestMethod -ModuleName 'PesterGherkinMockScopeTests'
$Table.Length | Should -BeExactly $Script:Context.GetVraResourceParameters.Length
# Looping through a) the parameters to be passed to Get-vRAResource, and the expected generated Url segment
for ($n = 0; $n -lt $Table.Length; $n++) {
$params = $Script:Context.GetVraResourceParameters[$n]
try {
Get-VraResource -vRASession $Script:Context.vRASession @params
# Create a relative Uri with the expected generated Api segment.
$ExpectedUriApiSegment = [Uri]::new([Uri]::EscapeUriString($Table[$n].UrlSubstring), [UriKind]::Relative)
# This should be the simplest thing I need to do to assert this mock was called
Assert-MockCalled Invoke-vRARestMethod -ModuleName PesterGherkinMockScopeTests -ParameterFilter {
Write-Host -Fore magenta "$ExpectedUriApiSegment"
"$ApiUriSegment" -ieq "$ExpectedUriApiSegment" }
# This should also work, I think..., but shouldn't be required
#----------------------------------------------------------------------------------------------
#Assert-MockCalled Invoke-vRARestMethod -ModuleName PesterGherkinMockScopeTests -ParameterFilter { "$ApiUriSegment" -ieq "$ExpectedUriApiSegment" } -Scope 0 #<-- This is current scope, right?
# And, after fixing Pester issue #1164, this should also work, but again, shouldn't be required:
#-----------------------------------------------------------------------------------------------
#Assert-MockCalled Invoke-vRARestMethod -ModuleName PesterGherkinMockScopeTests -ParameterFilter { "$ApiUriSegment" -ieq "$ExpectedUriApiSegment" } -Scope Scenario
# Since none of the above worked, now we use what we did in the When block above, to generate
# a script block containing the parameter filter test, as a closure, in the hopes that the local variable
# passed into the closure is captured, and now local to the closure itself, and therefore available to
# the parameter filter test script block.
#
# BUT, the scope will not contain either the local variable $ExpectedApiUriSegment above, nor the
# local variable inside the closure, $_ExpectedApiUriSegment.
#-----------------------------------------------------------------------------------------------
#$parameterFilter = &$Script:Context.ParameterFilterClosure $ExpectedUriApiSegment
#Assert-MockCalled 'Invoke-vRARestMethod' -ModuleName 'PesterGherkinMockScopeTests' -ParameterFilter $parameterFilter
# And once again, you could try scoping the same call above, but nothing changes
#----------------------------------------------------------------------------------------------
#Assert-MockCalled 'Invoke-vRARestMethod' -ModuleName 'PesterGherkinMockScopeTests' -ParameterFilter $parameterFilter -Scope 0
#Assert-MockCalled 'Invoke-vRARestMethod' -ModuleName 'PesterGherkinMockScopeTests' -ParameterFilter $parameterFilter -Scope Scenario # <-- Need to fix #1164 for this to work...
} catch {
# Catching the exception thrown by Assert-MockCalled so I can get _useful_ output... :|
if ($_.Exception) {
$message = $_.Exception.Message.Trim();
if ($message.StartsWith("Expected Invoke-vRARestMethod")) {
$message = "Expected Invoke-vRARestMethod in module PesterGherkinMockScopeTests to be called with '$([Uri]::EscapeUriString($Table[$n].UrlSubstring))'."
throw [Exception]::new($message, $_.Exception)
} else {
throw
}
} else {
throw
}
}
}
}
Getting scope from ScriptBlock 'Pester (42654543))'
Unbinding ScriptBlock from 'Pester (42654543))'
Setting ScriptBlock state from source state 'Unbound ScriptBlock from Mock (Unbound)' to 'Module - PesterGherkinMockScopeTests (10265017))'
Unbinding ScriptBlock from 'Pester (42654543))'
Setting ScriptBlock state from source state 'Unbound ScriptBlock from Test-ParameterFilter (Unbound)' to 'Caller - Captured in Invoke-Gherkin (61485005))'
Invoking scriptblock from location 'Mock - Parameter filter' in state 'Caller - Captured in Invoke-Gherkin (61485005))', 0 scopes deep:
{
[CmdletBinding(DefaultParameterSetName='Standard')] param (${ApiUriSegment},${vRASession},${Method})
Set-StrictMode -Off
$True
}
Setting ScriptBlock state from source state 'Pester (42654543))' to 'Module - PesterGherkinMockScopeTests (10265017))'
Invoking scriptblock from location 'Mock - of command Invoke-vRARestMethodfrom module PesterGherkinMockScopeTests' in state 'Module - PesterGherkinMockScopeTests (10265017))', 2 scopes deep:
{
}
Invoking scriptblock from location 'Mock - of command Invoke-vRARestMethod' in state 'Module - PesterGherkinMockScopeTests (10265017))', 3 scopes deep:
{
}
Unbinding ScriptBlock from 'Scenario (53986122))'
Setting ScriptBlock state from source state 'Unbound ScriptBlock from Test-ParameterFilter (Unbound)' to 'Caller - Captured in Invoke-Gherkin (61485005))'
+ Invoking scriptblock from location 'Mock - Parameter filter' in state 'Caller - Captured in Invoke-Gherkin (61485005))', 0 scopes deep:
{
[CmdletBinding(DefaultParameterSetName='Standard')] param (${ApiUriSegment},${vRASession},${Method})
Set-StrictMode -Off
Write-Host -Fore magenta "$ExpectedUriApiSegment"
"$ApiUriSegment" -ieq "$ExpectedUriApiSegment"
}
+ Hello
[-] Then the Url generated for the search query should be:... 254ms
at <ScriptBlock>, C:\Projects\PesterGherkinMockScopeTests\specs\features\steps\Get-vRAResource.Steps.ps1: line 101
101: throw [Exception]::new($message, $_.Exception)
From C:\Projects\PesterGherkinMockScopeTests\specs\features\Get-vRAResource.feature: line 17
Expected Invoke-vRARestMethod in module PesterGherkinMockScopeTests to be called with '/catalog-service/api/consumer/resourceViews'.
Testing completed in 526ms
Scenarios Passed: 0 Failed: 1
Steps Passed: 3 Failed: 1 Skipped: 0 Pending: 0 Inconclusive: 0 As you can hopefully see, almost before the output of the whole step it says
And then at bottom it says:
So the Step and the Filter are both invoked in different scopes. This I knew pretty much from the start, what was more suprising is where the filter actually invokes. The 0 scope is the invocation script (in your case it would probably be the command you pass to VS code, I have run it from a script like this: cd C:\Projects\PesterGherkinMockScopeTests
#get-item variable:ExpectedUriApiSegment | Remove-Item -force
Get-Module Pester | Remove-Module
Import-Module C:\Projects\pester_main\Pester.psm1
Get-Module PesterGherkinMockScopeTests | remove-module
$ExpectedUriApiSegment = "Hello"
Invoke-Gherkin -Path ./specs/features -ExcludeTag ignore -PesterOption (New-PesterOption -TestSuiteName 'PesterGherkinMockSopeTests' -IncludeVSCodeMarker) And the |
@nohwnd Thanks for looking at this! I thought this would be an interesting problem. So, I had some time to review what you posted above last night. I can't say that I completely understand it myself--I understand at a very high, conceptual level, but I don't truly "grok" what you have found. I'll take a look at your code and see if I can make heads or tails of it. My understanding of PowerShell scoping is a bit weak, and getting into the internals of PowerShell itself even more so. But, this has been such a frustrating, but interesting problem, I'm looking forward to getting to know PowerShell more intimately because of it! |
@fourpastmidnight I'll try to explain this in more detail. There are session states and scopes, I might be using the words interchanably in the above description of the problem, so here is a more precise definition. Session state is like a silo in which all variables, functions and other "resources" reside, and that is attached to one place such as module. -> An example of having two session states is making a module and invoking a function exported from that module from a script. That script is one session state (you can think about it as anonymous module if you want), and the module is another session state. This allows you to have internal variables and functions within a module that are not visible to other modules. Scope is like a level in that silo. On each level you can define functions and variables and if they are the same as variables on the levels below your level you shadow them. There a multiple ways to create a new scope, caling a function is one of them. Another is just invoking a script block: $a = 1
& {
$a = 2
& {
$a
}
$a
}
$a This piece of code nests two scopes and if you run it you can see how When you jump between session states you can define new scopes so new variables are available (or shadowed) within that session state, in the same way I did in the previous example. So I can take a scriptblock associate it to the Script (I call it Caller in the output because it is more accurate) session state and define a new variable there and it will stay there until that scope dies. This has some implications for your Gherkin code, from what I understood the Gherkin runtime captures steps to be run and then invokes them in one place in the top-level scope. So going back to similar structure like the example above: $script:filter = $null
$a = 1
& {
$a = 2
# captures it here
$script:filter = { # param filter
$a
}
}
# BUT invokes it here
&$script:filter The invoked script does not run within the place where $a is defined as 2, but instead it runs in place where it is defined as 1. (or in your case where the filter parameter no longer exists. To make this even more complicated, as you can see from the output above the scenario and the paramter filter are invoked in two different session states. So the actual invocation looks more like this: Keep in mind that this iis pseudo code, invoking it won't give you the correct results because the session state transitions are not implemented in the example: $a = 'hello'
$script:filter = $null
$script:steps = @()
# this is in caller scope -> where filter runs
& {
# we transitioned to Scenario scope (empty module)
# this variable will be defined in Scenario scope
$a = 2
# capture the step
$script:steps += { <# some step definition #> }
# capture the filter
$script:filter = { # param filter
$a
}
}
# invoke step here
& {
# invoke step here in Scenario scope $a is no longer defined that scope died
# you would need $script:a = 2 to make it available here
# $a will most likely be 'hello' here, because the top level scope will be above the
# scenario module session state
& $script:steps[0]
&{
# invoke filter in caller scope, $a = 'hello' is defined because we are running in caller session state
# and our scope is inheriting from the top-level scope where $a = 'hello' is defined
&$script:filter
}
} |
@nohwnd: Oh wow! I could see the session states being bound/unbound/added/removed and knew they had something to do with PowerShell scopes, but I hadn't had time to look into it further with the upcoming holiday here in the States. But your explanation was clear, so thank you!
I missed the fact that Gherkin was creating so many separate session states, but my investigation was leading me there. What really hampered my investigation is not knowing how session states worked and/or the lack of information about them (the MSDN page on them is surprisingly short and terse).
I need to pull down the latest module code and run my code with it again. Thank you again for the very concise and clear explanation! That was very helpful in furthering my understanding of PowerShell.
|
@nohwnd I'm not sure what the state of this is. I saw that you put some code into Master pursuant to the above. So, I grabbed the latest 4.5.0-beta2 code, but my tests are still failing. So I set Here's the error I'm getting (I've included a bit of the Scope hints, because they may be important):
So, the immediate thing that jumps out to me is the actual line that's being reported as the source of the error, 1236 in Mock.ps1. Here, I think what it's complaining about is But, looking at those scope hints above the error, I see where we set the ScriptBlock state from BTW, I ran this code two different ways when asserting the mock:
So, because of this new error with the null array, I'm not convinced that I'm actually testing the code changes you made with respect to setting script block session state/scope. I'll take a closer look at the changes you made to see if there are any obvious differences, but on the surface, I don't see anything overtly suspicious. |
OK, so one of my hypotheses about the cause has proved to not be the case--it's not because if ($ArgumentList.Length -gt 0) {
& $cmd @BoundParameters @ArgumentList
} else {
& $cmd @BoundParameters
} But the code still failed with the exact same error: |
AH! I know what the issue is. Here's my parameter filter: -ParameterFilter { "$ApiUriSegment" -ieq "$([Uri]::new([Uri]::EscapeUriString($Table[$n].UrlSubstring)))" } The "null array" is |
I quickly re-read your explanation above, and on my first reading, I apparently glossed over some salient points. It appears that, while you implemented some hints and what not, the way mocks work (for Gherkin) has not really changed a great deal--which explains my current error--it's really the same error I was getting originally before I went the route of trying to explicitly create closures. So if I were to do the whole closure thing again, I should get similar errors I was getting back on Dec. 14th/15th where whatever I'm trying to compare would be null since it won't exist in the session state (and by extension, scope) that the parameter filter is running in. Hmm, this will be a tricky issue to resolve. |
@nohwnd: Well, for the particular test in question, realizing what the issue really is (and better understanding it), and also realizing that I'm just trying to perform a string comparison for the parameter filter of the mock, I was able to modify my code: $ParameterFilterScriptBlock = [ScriptBlock]::Create("{ `"`$ApiUriSegment`" -ieq `"`$([Uri]::new([Uri]::EscapeUriString(`"$($Table[$n].UrlSubString)`")))`" }")
Assert-MockCalled Invoke-vRARestMethod -ModuleName 'MyModule.DecommissionServer' -ParameterFilter $ParameterFilterScriptBlock This creates a string containing the expanded While this works for this particular case, this is not "the solution"; so we need to continue to determine how to correctly set the state for parameter filters for Mocks (and possibly other states for mocks when used with Gherkin style tests??) One question that came to mind is why we're creating so many scopes for Gherkin style tests. Looking at The Cucumber Book, steps are global--any scenario can make use of any step found during execution according to the options specified for cucumber. Anyway, the point is, however, that when running a Scenario, a step can take it's argument and store it in some "instance variable" (to quote the book), which is then made available for all future steps defined in the scenario. Note that this includes background steps, too. So it would seem that steps should not have their own isolated session state, but should execute within the context of the scenario itself. How would this look in PowerShell? Not sure. Does this mean that during execution of steps we're always setting the step definition session state to be the session state of the enclosing scenario? This seems to make the most sense to me. This may resolve a lot of scoping issues that have cropped up in the issues list, such as #1071, where I resort to using |
In version 4.4.3 of Pester, in |
Getting the scopes right is difficult, but I try to do right in v5 🙂 Describe 'b' {
Write-Host "This is Describe"
BeforeAll { Write-Host "This is Setup" }
It 'i' { }
} Invoking this in v4:
Invoking this in v5:
and that is just the order. The scoping is different story. But I hope I am getting this right, so it will also work correctly for Gherkin when it's implemented on top of the new core. |
We should talk re: New core. I was going down the track of redoing a large part of Gherkin side due to test execution concerns and console output, which are fairly different from how RSpec style tests handle these concerns. As I study the Gherkin code more and more, it just looks like long term, the Gherkin code will be very unmaintainable going forward (at least, from the perspective of current v4 Pester). For what it is now, it worked so far as it has been implemented. But to get the robustness and nice output that people who’ve used cucumber would expect, the current Gherkin “framework” is not quite modeled correctly. And most importantly, I just don’t want to step on your toes, or vice versa 😉, whatever the case may be.
…Sent from my Windows 10 phone
From: Jakub Jareš
Sent: Monday, January 14, 2019 15:02
To: pester/Pester
Cc: Craig E. Shea; Mention
Subject: Re: [pester/Pester] Question: Is it possible to "parameterize" theParameterFilter for Mocks? (#1162)
Getting the scopes right is difficult, but I try to do right in v5 🙂
Describe 'b' {
Write-Host "This is Describe"
BeforeAll { Write-Host "This is Setup" }
It 'i' { }
}
Invoking this in v4:
Describing b
This is Setup
This is Describe
[?] i 1.26s
Invoking this in v5:
Describing b
This is Describe
This is Setup
[+] i 27ms
and that is just the order. The scoping is different story. But I hope I am getting this right, so it will also work correctly for Gherkin when it's implemented on top of the new core.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
|
@fourpastmidnight we totally should, and if you think in the mean time how you want the scoping to work, and the output then even better. :) |
Hmm, I just found something interesting I'd glossed over before in #Line 1081
$scope = if ($pester.InTest) { $null } else { $pester.CurrentTestGroup } At this line, |
And to elaborate on the above, here's my call stack:
What I found is that when I looked into the This may not be earth-shattering to anyone who's looked in-depth at this issue--but since I'm still learning my away around PowerShell, session states and scopes, I found this of interest as it relates to this issue. |
Oh, wait, that |
Ok, one more observation/question, so if the parameter Ok, and expanding on this, looking at |
Hold on--never mind the above. The VS Code PowerShell debugging is very wonky and not to be trusted (at least, for debugging complex issues like session state in Pester). When I executed line 1081, |
@fourpastmidnight That |
I've been working more with Mocks recently and I finally got a PowerShell closure to work! I thought I was going mad and just didn't understand PowerShell closures even though I quite understand them in other languages. Anyway, long story short, I got it to work with the Since I got closures to work for that parameter (meaning, I could close over state from my step definitions to use inside the script block to Alas, no. But, this is a good thing! It tells me that the way the script block is being set up for use by |
@fourpastmidnight Your observations are correct. When MockWith is a closure then it is kept as is, on the other hand the ParameterFilter is not. Instead the body of the parameter filter is concatenated with a |
1. General summary of the issue
OK, so I know on face value, the answer to this question is No. But hear me out. I'm using Gherkin style tests and I have several parameter variations for a call to a function within my module. So in my Given step, I have a table which has the parameter values for each call, and in my Then step I have another table that has the resulting parameter values I expect. More details are in order, but as this is a summary section, see below for more information.
2. Describe Your Environment
Pester version : 4.4.2 C:\Program Files\WindowsPowerShell\Modules\Pester\4.4.2\Pester.psd1
PowerShell version : 5.1.17134.165
OS version : Microsoft Windows NT 10.0.17134.0
3. Current Behavior
OK, so one note, the names are not great--still learning how to use Pester and Gherkin style tests within Pester and so, well, things change a lot right now.
Here's my Gherkin file:
Here are the When and Then step definitions which are relevant, of things that I've tried:
I expected that by using a closure, I could capture the variable from
$Table[$n].UrlSubstring
(another poorly named item--but this is still a work in progress, mostly around getting a passing test!) I tried doing the simplest thing possible:-ParameterFilter { "$ApiUriSegment" -ieq "$([Uri]::new([Uri]::EscapeUriString($Table[$n].UrlSubstring)))" }
, however, whenTest-ParameterFilter
executes,$Table
is nowhere to be found in any scope whatsoever. Hence my attempt to use closures to capture the value that I want.4. Expected Behavior
I expected, then, that by capturing
$Table[$n].UrlSubstring
in$ExpectedApiUriSegment
inside the closure, that whenTest-ParameterFilter
executes,$ExpectedApiUriSegment
would be in the local scope. But unfortunately, it is not.Is there any way to do what I want at all? I fear I may be too close to the trees to see the forest.
The text was updated successfully, but these errors were encountered: