Closed johndog closed 1 year ago
Thanks for reporting, will have a look.
Cannot reproduce this in 4.10.1 nor 5.0.2 (when putting the function and mock into BeforeAll). If you can reproduce on the latest version please provide full repro.
Still reproduces for me, on two different computers:
PS C:\Users\johnd> invoke-pester .\OneDrive\repos\pshlib\bugs\parm.tests.ps1
Pester v4.10.1
Executing all tests in '.\OneDrive\repos\pshlib\bugs\parm.tests.ps1'
Executing script .\OneDrive\repos\pshlib\bugs\parm.tests.ps1
[-] should have Parameter1 178ms
RuntimeException: You cannot call a method on a null-valued expression.
at Should-HaveParameter, C:\Program Files\WindowsPowerShell\Modules\Pester\4.10.1\Functions\Assertions\HaveParameter.ps1: line 142
at <ScriptBlock>, C:\Users\johnd\OneDrive\repos\pshlib\bugs\parm.tests.ps1: line 7
Tests completed in 1.01s
Tests Passed: 0, Failed: 1, Skipped: 0, Pending: 0, Inconclusive: 0
parm.tests.ps1 is just a literal paste of the repro code.
Both of these machines are running Windows Insider builds... I actually don't have anything else. Do you have a machine with an insider build?
PS C:\Users\johndog\OneDrive\repos\pshlib\bugs> $PSVersionTable
Name Value
---- -----
PSVersion 5.1.20180.1000
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 10.0.20180.1000
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
Still able to repro in 5.3.3. Get-Command
is lazy-loading the parameters. Can repro with Set-Alias
, but not built-in aliases as they've probably been called sometime. So CommandInfo
is passed to Should
without Parameters
being ready. Simply accessing it before calling Should
solves it. Ex.
Describe 'd1' {
BeforeAll {
function TestFunction($Parameter1) { }
Mock TestFunction {}
}
It 'should have Parameter1' {
Get-Command TestFunction | Where-Object Parameters | Should -HaveParameter 'Parameter1'
}
}
Similar to the PSv2-fix here, but it doesn't solve this one: https://github.com/pester/Pester/blob/dd7d09eb730c8d9fc7ac746aeecf750bf98b9d00/src/functions/assertions/HaveParameter.ps1#L192-L193 The original commit doesn't say much about it.
It looks like PowerShell tries to lazy-load the metadata for the original function when getting it's parameters. When doing that, PowerShell doesn't look in the local scope in the caller session state (testfile) were the function was created, so it can't find it. If TestFunction
was from a module, it would work - even with local alias.
PowerShell-native repro:
Get-Module funcModule | Remove-Module
New-Module funcModule {
function moduleFunc ($Param1234) { }
} | Import-Module
Get-Module testModule | Remove-Module
New-Module testModule {
function test($localAlias, $localFunc, $moduleFunc, $localAlias2) {
"Running in $($ExecutionContext.SessionState.Module.name)"
"Script Function $($localFunc.Name) contains parameters $($null -ne $localFunc.Parameters)"
"Script Alias $($localAlias.Name) contains parameters $($null -ne $localAlias.Parameters)"
"Module Function $($moduleFunc.Name) contains parameters $($null -ne $moduleFunc.Parameters)"
"Script Alias $($localAlias2.Name) for module func contains parameters $($null -ne $localAlias2.Parameters)"
#Try accessing AliasInfo.Parameters for the local function
$localAlias.Parameters.PSBase.ContainsKey('MyParam1')
}
} | Import-Module
# running in a local scope. dot-sourcing this would work because the function is created in the root
& {
function scriptFunc ($MyParam1) { }
Set-Alias -Name alias1 -Value scriptFunc
Set-Alias -Name alias2 -Value moduleFunc
test -localFunc (Get-Command scriptFunc) -localAlias (Get-Command alias1) -moduleFunc (Get-Command moduleFunc) -localAlias2 (Get-Command alias2)
}
# Output
Running in testModule
Script Function scriptFunc contains parameters True
Script Alias alias1 contains parameters False
Module Function moduleFunc contains parameters True
Script Alias alias2 for module func contains parameters True
InvalidOperation:
Line |
10 | $localAlias.Parameters.PSBase.ContainsKey('MyParam1')
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| You cannot call a method on a null-valued expression.
1. General summary of the issue
Title sums it up.
To reproduce, use Invoke-Pester on the following tests.ps1 code:
RuntimeException Result:
2. Describe Your Environment
Pester version : 4.9.0 C:\Program Files\WindowsPowerShell\Modules\Pester\4.9.0\Pester.psd1 PowerShell version : 5.1.19041.1 OS version : Microsoft Windows NT 10.0.19041.0
3. Expected Behavior
Should
should either reject the use of a mock explicitly, or treat the mock as if it was the original function and pass the test. A RunTime exception shouldn't leak through.5. Fix consideration
The exception occurs in this line in HaveParameter.ps1 (line 142):
$hasKey = $ActualValue.Parameters.PSBase.ContainsKey($ParameterName)
$ActualValue.Parameters
at this point is$null
, so the access to PSBase fails.The check for whether the function is mocked would need to take place earlier in the function, either to reject the use of Should, or to look up the underlying command and act on that.
6. Context
I was trying to write tests for a function. It wasn't necessary to test parameters on a mocked version of the function, but the nature of the error makes it hard to figure out why it doesn't work.