Skip to content
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

v5: InModuleScope fails to pass values from the current scope without explicit -Parameters #1603

Closed
vexx32 opened this issue Jun 16, 2020 · 11 comments · Fixed by #1957
Closed

Comments

@vexx32
Copy link
Contributor

vexx32 commented Jun 16, 2020

1. General summary of the issue

In V4, this worked:

It 'replaces wildcard characters with regex equivalent' -TestCases @(
    @{ Pattern = 'About*son'; Expected = '^About.*son$' }
    @{ Pattern = 'AboutArrays*'; Expected = '^AboutArrays' }
    @{ Pattern = '*Arrays'; Expected = 'Arrays$' }
    @{ Pattern = '*Array*'; Expected = 'Array' }
) {
    param($Pattern, $Expected)
    InModuleScope 'PSKoans' { ConvertFrom-WildcardPattern $Pattern } | Should -Be $Expected
}

My attempt at converting to V5 was:

It 'replaces wildcard characters with regex equivalent' -TestCases @(
    @{ Pattern = 'About*son'; Expected = '^About.*son$' }
    @{ Pattern = 'AboutArrays*'; Expected = '^AboutArrays' }
    @{ Pattern = '*Arrays'; Expected = 'Arrays$' }
    @{ Pattern = '*Array*'; Expected = 'Array' }
) {
    InModuleScope 'PSKoans' { ConvertFrom-WildcardPattern $Pattern } | Should -Be $Expected
}

This does not work. The following does work:

It 'replaces wildcard characters with regex equivalent' -TestCases @(
    @{ Pattern = 'About*son'; Expected = '^About.*son$' }
    @{ Pattern = 'AboutArrays*'; Expected = '^AboutArrays' }
    @{ Pattern = '*Arrays'; Expected = 'Arrays$' }
    @{ Pattern = '*Array*'; Expected = 'Array' }
) {
    InModuleScope 'PSKoans' -Parameters @{ Pattern = $Pattern } {
        param($Pattern)
        ConvertFrom-WildcardPattern $Pattern
    } | Should -Be $Expected
}

Also... it seems -ArgumentList does not work either. I tried this way with no success:

It 'replaces wildcard characters with regex equivalent' -TestCases @(
    @{ Pattern = 'About*son'; Expected = '^About.*son$' }
    @{ Pattern = 'AboutArrays*'; Expected = '^AboutArrays' }
    @{ Pattern = '*Arrays'; Expected = 'Arrays$' }
    @{ Pattern = '*Array*'; Expected = 'Array' }
) {
    InModuleScope 'PSKoans' -ArgumentList $Pattern {
        ConvertFrom-WildcardPattern $args[0]
    } | Should -Be $Expected
}

2. Describe Your Environment

Pester version     : 5.0.2 /Users/joelfrancis/.local/share/powershell/Modules/Pester/5.0.2/Pester.psd1
PowerShell version : 7.1.0-preview.3
OS version         : Unix 19.4.0.0

3. Expected Behavior

InModuleScope shouldn't require explicit parameter passing.

4.Current Behavior

InModuleScope requires explicit passing of parameters as a hashtable; even -ArgumentList doesn't work.

5. Possible Solution

6. Context

Converting module tests to v5. 🙂

@nohwnd
Copy link
Member

nohwnd commented Jun 16, 2020

@vexx32 are you sure that test worked in v4? Because the $Pattern would be defined in script session state, while the ScriptBlock would be invokes in module scope. Can you put breakpoint on the ConvertFrom-WildcardPattern $Pattern line and look at the value of $Pattern?

This shows that $Pattern is null, in v4, as expected:

Get-Module PSKoans | Remove-Module
New-Module -name PSKoans -scriptblock { function ConvertFrom-WildcardPattern ($Pattern) { Write-Host $Pattern ; $Pattern } }  -PassThru | Import-Module

describe 'a' {
It 'replaces wildcard characters with regex equivalent' -TestCases @(
    @{ Pattern = 'About*son'; Expected = '^About.*son$' }
    @{ Pattern = 'AboutArrays*'; Expected = '^AboutArrays' }
    @{ Pattern = '*Arrays'; Expected = 'Arrays$' }
    @{ Pattern = '*Array*'; Expected = 'Array' }
) {
    param ($Pattern, $Expected)
    InModuleScope 'PSKoans' { 
        ConvertFrom-WildcardPattern $Pattern 
    } | Should -Be $Expected
}} 

Is maybe the whole test running in PSKoans module?

@vexx32
Copy link
Contributor Author

vexx32 commented Jun 16, 2020

🤔 oh, huh! Yeah, come to think of it, I think I probably had the entire test file here running in module scope. Was trying to avoid that this time around.

So for this kind of thing I do need to explicitly insert parameters?

@nohwnd
Copy link
Member

nohwnd commented Jun 16, 2020

Yes for this you should be forced to pass params explictly via the -Parameters parameter, because you are crossing a session state boundary. But you should not be forced to specify the param block, which does not work at the moment. So needing to specify the param block manually is a bug. The other thing is not.

@vexx32
Copy link
Contributor Author

vexx32 commented Jun 16, 2020

That makes sense, thanks!

@rmbolger
Copy link

Please forgive the slight issue hijack, but I have a related question. In @vexx32's example that does work, is there any way to use variables that were defined outside of the TestCases array definition within the array? I have some expensive-to-initialize test data that I'm trying to assign to a variable in a BeforeAll block and then insert it into the TestCases array definition. But all I get is $null within the test.

In other words:

Describe 'a' {

    BeforeAll {
        $expensive = Get-ExpensiveData
    }

    It 'Does stuff' -TestCases @(
        @{ thing1 = 'blah1'; thing2 = $expensive }
        @{ thing2 = 'blah2'; thing2 = $expensive }
        @{ thing3 = 'blah3'; thing2 = $expensive }
    ) {
        InModuleScope MyModule -Parameters @{thing1=$thing1;thing2=$thing2}  {
            param($thing1,$thing2)
            Invoke-Stuff $thing1, $thing2 | Should -Be 'blah'
        }
    }
}

All of the thing2 instances end up as $null within the test. If I just run Get-ExpensiveData for each instance inside the array directly, everything works. But I'd prefer not to.

The other thing I've considered doing as sort of a hacky workaround is defining an empty private function within my module that I can mock specifically to pass data into the module scope from outside the module scope.

@nohwnd
Copy link
Member

nohwnd commented Oct 10, 2020

Yes, but you'd put that code outside of BeforeAll. Or in Pester 5.1.0-beta1 and newer, into BeforeDiscovery block. This will define them during discovery and you can then pass them to the tests using testcases as you are doing just now. It will also run just once.

@rmbolger
Copy link

Oh sweet. For some reason, I thought all initialization stuff had to occur in a Before* block now.

@vexx32
Copy link
Contributor Author

vexx32 commented Oct 10, 2020

-TestCases and other things that happen outside a Before* or It block are a completely separate step now, so if you want to do setup for -TestCases they need to be out of those blocks or in the BeforeDiscovery block as @nohwnd mentioned.

You can't directly mix -TestCases with stuff that happens in a Before* block though, you'd have to pull those values in as variables inside the It block directly rather than with -TestCases.

@JustinGrote
Copy link
Contributor

JustinGrote commented Jan 27, 2021

@nohwnd resurrecting this issue, currently this is what is required to work:

It 'has the mandatory value of parameter "<Name>" set to "<Mandatory>"' -TestCases @(
    @{Name = 'Name'; Mandatory = $False }
    @{Name = 'VaultName'; Mandatory = $False }
    @{Name = 'AdditionalParameters'; Mandatory = $False }
) {
    InModuleScope 'SecretManagement.KeePass.Extension' -Parameters @{Name=$Name; Mandatory=$Mandatory} {
        param($Name,$Mandatory)
        $testAttribute = ((Get-Command -Module $ExtModuleName -Name $FunctionName).Parameters[$Name].Attributes
            | Where-Object {$PSItem -is [System.Management.Automation.ParameterAttribute]}).Mandatory
        $testAttribute | Should -Be $Mandatory
    }
}

In my opinion this is too much boilerplate when testcases exist. I would propose the following solution:

  1. Expose a $pesterCurrentTestCase (or whatever) hashtable to the it scope
  2. At the InModuleScope statement, only if $pesterCurrentTestCase exists, implicity bring it into the module scope as named parameters or variables.

The end result being that this would work:

It 'has the mandatory value of parameter "<Name>" set to "<Mandatory>"' -TestCases @(
    @{Name = 'Name'; Mandatory = $False }
    @{Name = 'VaultName'; Mandatory = $False }
    @{Name = 'AdditionalParameters'; Mandatory = $False }
) {
    InModuleScope 'SecretManagement.KeePass.Extension' {
        $testAttribute = ((Get-Command -Module $ExtModuleName -Name $FunctionName).Parameters[$Name].Attributes
            | Where-Object {$PSItem -is [System.Management.Automation.ParameterAttribute]}).Mandatory
        $testAttribute | Should -Be $Mandatory
    }
}

@fflaten
Copy link
Collaborator

fflaten commented May 24, 2021

@JustinGrote The testcase hashtable is already available as $_ in your test. When parameters are automatically made variables inside InModuleScope per comment above then you can run:

It 'has the mandatory value of parameter "<Name>" set to "<Mandatory>"' -TestCases @(
    @{Name = 'VaultName'; Mandatory = $False }
) {
    InModuleScope 'SecretManagement.KeePass.Extension' -Parameters $_ {
        $Name | Should -Be 'VaultName'
        $Mandatory | Should -Be $False
    }
}

InModuleScope is a general public function, so trying to keep it simple. This feels like a good middleground (to me at least).

@JustinGrote
Copy link
Contributor

@fflaten the -Parameters on InModuleScope was what I was missing, thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants