Ash
Ash

Reputation: 3246

Why can I not test for ParameterBindingValidationException in Pester?

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

Answers (2)

mklement0
mklement0

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.

    • While you can obtain an object's non-public type via .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

Ash
Ash

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

Related Questions