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