Reputation: 3246
Given a function that has validation for a parameter:
function Test-Validation {
[CmdletBinding()]
param (
[Parameter()]
[ValidateScript({
# Add some validation that can throw.
if (-not (Test-Path -Path $_ -PathType Container)) {
throw "OutDir must be a folder path, not a file."
}
return $true
})]
[System.String]
$Folder
)
Process {
$Folder + " is a folder!"
}
}
We should be able to check the error type and set that as the ExpectedType
in a Pester Test.
Test-Validation -Folder C:\Temp\file.txt
Test-Validation : Cannot validate argument on parameter 'Folder'. OutDir must be a folder path, not a file.
At line:1 char:17
+ Test-Validation C:\Temp\file.txt
+ ~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Test-Validation], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Test-Validation
$Error[0].Exception.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
False True ParameterBindingValidationException System.Management.Automation.ParameterBindingException
However, when testing in Pester, the test fails because it cannot find the type.
$ShouldParams = @{
Throw = $true
ExpectedMessage = "Cannot validate argument on parameter 'OutDir'. OutDir must be a folder path, not a file."
ExceptionType = ([System.Management.Automation.ParameterBindingValidationException])
}
{ Test-Validation -Folder C:\Temp\file.txt } | Should @ShouldParams
# Result
RuntimeException: Unable to find type [System.Management.Automation.ParameterBindingValidationException].
How can I fix this test so that I know I am not just catching any exception type?
Upvotes: 1
Views: 3259
Reputation: 439487
To generalize your own answer a bit:
Since working with the types of objects starting from a given instance isn't that common in PowerShell, the fact that an instance's type may be non-public isn't usually obvious, as long as the type derives from (is a subclass of) the expected public type.
.GetType()
, you cannot refer to it via a type literal (e.g. [System.Management.Automation.ParameterBindingException]
), such as for use in Pester tests or parameter declarations.You can call .GetType().IsPublic
on any given instance to check whether its type is public, and .GetType().BaseType
to get that type's base type - though you may have to call the latter multiple types until you reach a type for which .IsPublic
is $true
- see the convenience function at the bottom.
In the case at hand .GetType().BaseType.FullName
is sufficient to reach the public base type:
# Provoke a non-public [System.Management.Automation.ParameterBindingValidationException]
# exception.
try { & { param([ValidateScript({ $false })] $foo)} bar } catch { $err = $_ }
# Output the full name of the exception type underlying the
# statement-terminating error that the failed validation reported:
$err.Exception.GetType().FullName
# It is only its *base* type that is public and therefore usable as a type
# literal ([...]), such as in a Pester test.
$err.Exception.GetType().BaseType.FullName
The above yields:
System.Management.Automation.ParameterBindingValidationException # non-public
System.Management.Automation.ParameterBindingException # public base type
Below is convenience function Get-PublicType
, which, given any instance, reports the most derived type in the inheritance chain of the instance's type that is public (which may be the instance's type itself:
Sample call:
PS> Get-PublicType $err.Exception
PublicType NonPublicDerivedType Instance
---------- -------------------- --------
System.Management.Automation.ParameterBindingException {System.Management.Automation.ParameterBindingValidationException} System.Management.Automation.ParameterBindingValidationException: Cannot validate argument on par…
Get-PublicType
source code:
function Get-PublicType {
[CmdletBinding()]
param(
[Parameter(Mandatory, ValueFromPipeline)]
$Instance
)
process {
$type = $Instance.GetType()
$nonPublicTypes = @()
while (-not $type.IsPublic) {
$nonPublicTypes += $type
$type = $type.BaseType
}
# $type.FullName
[pscustomobject] @{
PublicType = $type
NonPublicDerivedType = $nonPublicTypes
Instance = $Instance
}
}
}
Upvotes: 2
Reputation: 3246
The reason why you cannot capture this type is because it is not a public class within [System.Management.Automation]
. Instead you can set the -ExceptionType
to the class it derives from [System.Management.Automation.ParameterBindingException]
and your test will now pass with validation for the exception type thrown.
$ShouldParams = @{
Throw = $true
ExpectedMessage = "Cannot validate argument on parameter 'OutDir'. OutDir must be a folder path, not a file."
ExceptionType = ([System.Management.Automation.ParameterBindingException])
}
{ Test-Validation -Folder C:\Temp\file.txt } | Should @ShouldParams
Upvotes: 2