pester/Pester

InModuleScope does not work in discovery when module is not imported in session

johlju opened this issue · 4 comments

1. General summary of the issue

Importing the module being tested in in the file setups BeforeAll-block and having an InModuleScope referencing that module makes discovery unable to run.

2. Describe Your Environment

Using Pester 5 RC8

Pester version     : 5.0.0 C:\source\SqlServerDsc\output\RequiredModules\Pester\5.0.0\Pester.psd1
PowerShell version : 7.0.1
OS version         : Microsoft Windows NT 10.0.19041.0

3. Expected Behavior

Discovery should run successfully.

4.Current Behavior

The following mockup

BeforeAll {
    Import-Module -Name '.\output\SqlServerDsc\14.0.0\Modules\SqlServerDsc.Common'
}

Describe 'SqlServerDsc.Common\Copy-ItemWithRobocopy' -Tag 'CopyItemWithRobocopy' {
    BeforeAll {
    }

    Context 'When Copy-ItemWithRobocopy throws an exception it should return the correct error messages' {
        InModuleScope 'SqlServerDsc.Common' {
            It 'Should' {
                $true | Should -BeTrue
            }
        }
    }
}

Fails with an error.

Discovery: Starting test discovery in 1 test containers.

Starting discovery in 1 files.
Discovery: Discovering tests in C:\source\SqlServerDsc\tests\Unit\Mockup2.Tests.ps1
Discovering in C:\source\SqlServerDsc\tests\Unit\Mockup2.Tests.ps1.
System.Management.Automation.RuntimeException: No modules named 'SqlServerDsc.Common' are currently loaded.
at Get-ScriptModule, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.0.0\Pester.psm1: line 7844
at InModuleScope, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.0.0\Pester.psm1: line 7803
at <ScriptBlock>, C:\source\SqlServerDsc\tests\Unit\Mockup2.Tests.ps1: line 10
at New-Block, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.0.0\Pester.psm1: line 691
at Context, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.0.0\Pester.psm1: line 6457
at <ScriptBlock>, C:\source\SqlServerDsc\tests\Unit\Mockup2.Tests.ps1: line 9
at New-Block, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.0.0\Pester.psm1: line 691
at Describe, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.0.0\Pester.psm1: line 7467
at <ScriptBlock>, C:\source\SqlServerDsc\tests\Unit\Mockup2.Tests.ps1: line 5
at <ScriptBlock>, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.0.0\Pester.psm1: line 2724
at Invoke-File, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.0.0\Pester.psm1: line 2733
at Invoke-BlockContainer, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.0.0\Pester.psm1: line 2662
at Discover-Test, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.0.0\Pester.psm1: line 1289
at Invoke-Test, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.0.0\Pester.psm1: line 2195
at Invoke-Pester<End>, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.0.0\Pester.psm1: line 3995
at <ScriptBlock>, <No file>: line 1

5. Possible Solution

None for the discovery part. But the reason to use InModuleScope here would help with another way to get to the script variable mentioned in the section Context below.

6. Context

Unable to use InModuleScope which is primarily used to get a script variable ($script:localizedData) from the module which contains the localized strings. That is in turn used to evaluate that the correct error messages are returned in error handling.

The problem here is that InModuleScope runs during Discovery, and BeforeAll runs after Discovery (during Run). So to solve this in general you would need to import the module during discovery to make it available for InModuleScope.

The upside of this is that your It ScriptBlock would be associated with the SqlServerDsc.Common session state and so you'd be able to access all of it's internals.

The downside of that is that all the work that happens on import of SqlServerDsc.Common will have to happen during discovery, adding unnecessary work when you filter out all the tests in that file (or in the future just do test discovery but not run).


In this particular case I would suggest to put the InModuleScope inside of the It block, or inside of the BeforeAll to resolve the variable.

BeforeAll {
    Import-Module -Name '.\output\SqlServerDsc\14.0.0\Modules\SqlServerDsc.Common'
}

Describe 'SqlServerDsc.Common\Copy-ItemWithRobocopy' -Tag 'CopyItemWithRobocopy' {
    BeforeAll {
    }

    Context 'When Copy-ItemWithRobocopy throws ...' {
        It 'Should' {
            InModuleScope 'SqlServerDsc.Common' {
                $true | Should -BeTrue
            }
        }
    }
}

Would that solve your issue, or is it more complicated than that in the real life?

@nohwnd your proposal to move the InModuleScope block inside the BeforeAll doesn't work though I'm adding extra mock expectations here.

BeforeAll {
    & $PSScriptRoot\..\..\..\Modules\Import-M1.ps1
    . $PSScriptRoot\..\..\Cmdlets-Helpers\Get-RandomValue.ps1

    $mockedValue=Get-RandomValue -String
    InModuleScope M1 {
        Mock Get-M1Private {
            $mockedValue
        }
    }

}
Describe -Tag @("M1","Module","InModuleScope","MockPrivate") "InModuleScope M1 Mock private" {
    BeforeAll {
        #Doesn't work here either!
    }
    It "Get-M1Private Mocked" {
        Get-M1Private| Should -BeExactly $mockedValue
    }
    It "Get-M1" {
        Get-M1| Should -BeExactly $mockedValue
    }
}

AfterAll {
    Remove-Module -Name M1 -Force
}

@Sarafian I think v5 handles this the best, but it is a a hill to climb to understand in what scope things are. In your example I think the problem is that $mockedValue is outside the module scope. I think you want this.

BeforeAll {
    & $PSScriptRoot\..\..\..\Modules\Import-M1.ps1

    InModuleScope M1 {
      . <full path to script>\Get-RandomValue.ps1
  
      $mockedValue=Get-RandomValue -String

        Mock Get-M1Private {
            $mockedValue
        }
    }
}

Or another alternative, if you want to use $mockedValue outside the InModuleScope (for example in a Should) is to pass in a parameter to the InModuleScope.

BeforeAll {
    & $PSScriptRoot\..\..\..\Modules\Import-M1.ps1
    . $PSScriptRoot\..\..\Cmdlets-Helpers\Get-RandomValue.ps1

    $mockedValue=Get-RandomValue -String

    $inModuleScopeParameters = @{
        MockedValue = $mockedValue
    }

    InModuleScope -ModuleName M1 -Parameters $inModuleScopeParameters -ScriptBlock {
        # This should be able to be removed in a future version of Pester.
        param
        (
            $MockedValue
        )

        Mock Get-M1Private {
            $MockedValue
        }
    }
}

Thank you. Followed your example and made the following adaptation

Describe -Tag @("M1","Module","InModuleScope","MockPrivate") "InModuleScope M1 Mock private" {
    BeforeEach {
        $mockedValue=Get-RandomValue -String
        Write-Host "BeforeEach-mockedValue=$mockedValue"
        Mock -ModuleName M1 Get-M1Private {
            $mockedValue
        }
        $inModuleScopeParameters = @{
            MockedValue = $mockedValue
        }

    }
    It "Get-M1Private Mocked" {
        Write-Host "IT-mockedValue=$mockedValue"
        Write-Host "IT-inModuleScopeParameters.MockedValue=$($inModuleScopeParameters.MockedValue)"
        InModuleScope M1 -Parameters $inModuleScopeParameters {
            Write-Host "InModuleScope-mockedValue=$mockedValue"
            Get-M1Private| Should -BeExactly $mockedValue
        }
    }
    It "Get-M1" {
        Get-M1| Should -BeExactly $mockedValue
    }
}

In essence feeding the InModuleScope's scope with the mockedValue variable but the outcome is still the same

Describing InModuleScope M1 Mock private
<ScriptBlock>: BeforeEach-mockedValue=Random-aeMIE
<ScriptBlock>: IT-mockedValue=Random-aeMIE
<ScriptBlock>: IT-inModuleScopeParameters.MockedValue=Random-aeMIE
<ScriptBlock>: InModuleScope-mockedValue=
  [-] Get-M1Private Mocked 57ms (54ms|3ms)
   Expected exactly $null, but got 'Random-aeMIE'.
   at Get-M1Private| Should -BeExactly $mockedValue, C:\Users\asara\Code\GitHub\PowerShellTemplate\Src\Tests\Modules\M1\Test-M1.Tests.ps1:35
   at <ScriptBlock>, C:\Users\asara\Code\GitHub\PowerShellTemplate\Src\Tests\Modules\M1\Test-M1.Tests.ps1:35

Notice that just before the InModuleScope the hash is correct and within the InModuleScope is gone again.

Anyhow, I've created a bug for this #2009 and we can continue there