Mocking with `-ModuleName` fails when mocked function has a parameterized type internal to the module.
Checklist
- [X] Issue has a meaningful title
- [X] I have searched the existing issues. See all issues
- [X] I have tested using the latest version of Pester. See Installation and update guide.
What is the issue?
I was attempting to create a pester test for a function that calls an internal module function that has a List parameter of an internal module class. Note: This works fine if the parameter is NOT a list, i.e. single object or array of objects.
Starting discovery in 1 files.
Discovery found 1 tests in 62ms.
Running tests.
[-] Describe Will not work failed
ParseException: At line:3 char:66
+ … [System.Collections.Generic.List`1[[Parameterized, PowerShell Class A …
+ ~
Missing ] at end of type token.
At line:3 char:66
+ … [System.Collections.Generic.List`1[[Parameterized, PowerShell Class A …
+ ~
Missing ] at end of attribute or type literal.
At line:3 char:67
+ … System.Collections.Generic.List`1[[Parameterized, PowerShell Class As …
+ ~
Parameter declarations are a comma-separated list of variable names with optional initializer expressions.
At line:3 char:67
+ … System.Collections.Generic.List`1[[Parameterized, PowerShell Class As …
+ ~
Missing ')' in function parameter list.
MethodInvocationException: Exception calling "Create" with "1" argument(s): "At line:3 char:66
+ … [System.Collections.Generic.List`1[[Parameterized, PowerShell Class A …
+ ~
Missing ] at end of type token.
At line:3 char:66
+ … [System.Collections.Generic.List`1[[Parameterized, PowerShell Class A …
+ ~
Missing ] at end of attribute or type literal.
At line:3 char:67
+ … System.Collections.Generic.List`1[[Parameterized, PowerShell Class As …
+ ~
Parameter declarations are a comma-separated list of variable names with optional initializer expressions.
At line:3 char:67
+ … System.Collections.Generic.List`1[[Parameterized, PowerShell Class As …
+ ~
Missing ')' in function parameter list."
Tests completed in 198ms
Tests Passed: 0, Failed: 1, Skipped: 0 NotRun: 0
BeforeAll \ AfterAll failed: 1
- Will not work
Expected Behavior
The mock should be created without runtime errors.
Steps To Reproduce
PSTest.psm1
class Parameterized {
[string] $RandomData
}
function IDoStuffWithLists {
param (
[System.Collections.Generic.List[Parameterized]] $Things
)
}
function IDoStuffWithArrays {
param (
[Parameterized[]] $Things
)
}
function IDoStuff {
param (
[Parameterized] $Things
)
}
function ICallIDoStuff {
IDoStuffWithLists
IDoStuffWithArrays
IDoStuff
}
Export-ModuleMember -Function ICallIDoStuff
ICalIDoStuff.Tests.ps1
BeforeAll {
Import-Module PSTest -Force
}
Describe 'This is inconsistent' {
BeforeAll {
Mock IDoStuff -ModuleName 'PSTest' # Works
Mock IDoStuffWithArrays -ModuleName 'PSTest' # Works
Mock IDoStuffWithLists -ModuleName 'PSTest' # Fails
}
It 'Call function' {
ICallIDoStuff
}
}
Describe your environment
Pester version : 5.3.3 C:\Users\...\Documents\PowerShell\Modules\Pester\5.3.3\Pester.psm1
PowerShell version : 7.2.6
OS version : Microsoft Windows NT 10.0.19043.0
Possible Solution?
No response
Thanks for the report! Looks like this is because PowerShell creates a special FullName for classes created in modules especially. Simpler repro:
New-Module -Name abc {
class SomeClass {
[string] $RandomData
}
function someFunc {
param(
[SomeClass] $Param1
)
}
} | Import-Module
(& (Get-Module abc) { [someclass] }).FullName
<1e6b41c3>.SomeClass
Pester uses PowerShell's ProxyCommand API to create the mock's param-block. It uses the type's fullname which in these scenarios includes invalid characters (numbers, special characters etc) for the parameter-type.
[System.Management.Automation.ProxyCommand]::GetParamBlock((Get-Command someFunc))
[<941113fe>.SomeClass]
${Param1}
In your sample the equivalent is:
$metadata.Parameters.Things.ParameterType.FullName
System.Collections.Generic.List`1[[Parameterized, PowerShell Class Assembly, Version=1.0.0.5, Culture=neutral, PublicKeyToken=null]]
# which generates this param-block:
[System.Management.Automation.ProxyCommand]::GetParamBlock($metadata)
[System.Collections.Generic.List`1[[Parameterized, PowerShell Class Assembly, Version=1.0.0.5, Culture=neutral, PublicKeyToken=null]], System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]
${Things}
Not sure which part of this is considered a bug vs limitation, but I'll create an issue in PowerShell-repo. Using a generic type (List) didn't make this easier.
In my sample they could've simply used Name for the type, which would be SomeClass, but for your list that would be List`1 which wouldn't work.
We can try to rewrite this in Pester, just need to get the detection right so we don't modify and break anything else.
@fflaten thanks for the response, I switched over to using arrays, this is part of old code I am cleaning up and they no longer need to be mutable and it gets me past this issue. But I still thought it was worth mentioning as a limitation/bug.
Absolutely. 🙂 The issue isn't related to the use of List, but classes in general, so fixing this might be valuable for others too. Ex. this will currently fail:
Describe 'PowerShell classes == pain' {
BeforeAll {
New-Module -Name abc {
class SomeClass {
[string] $RandomData
}
function someFunc {
param(
[SomeClass] $Param1
)
}
function publicFunc {
someFunc
}
} | Import-Module
Mock someFunc # fails here
}
It 'Call function' {
publicFunc
}
}
# output
Starting discovery in 1 files.
Discovery found 1 tests in 205ms.
Running tests.
[-] Describe PowerShell classes == pain failed
ParseException: At line:3 char:6
+ [<a6061cc7>.<9fceeca4>.<3e98a71c>.SomeClass]
+ ~
Missing type name after '['.
At line:2 char:12
+ param (
+ ~
Missing ')' in function parameter list.
....lots of errors...
Btw. I forgot to mention a workaround for now, try -RemoveParameterType <paramName>. Ex
Mock IDoStuffWithLists -ModuleName 'PSTest' -RemoveParameterType Things