为什么我不能在 Pester 中测试 ParameterBindingValidationException?

Why can I not test for ParameterBindingValidationException in Pester?

给定一个对参数进行验证的函数:

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!"
    }
}

我们应该能够检查错误类型并将其设置为纠缠测试中的 ExpectedType

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

但是在Pester中测试时,由于找不到类型,测试失败。

$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].

我怎样才能修复这个测试,以便我知道我不仅仅是在捕获任何异常类型?

无法捕获此类型的原因是因为它不是 [System.Management.Automation] 中的 public class。相反,您可以将 -ExceptionType 设置为它派生自 [System.Management.Automation.ParameterBindingException] 的 class,您的测试现在将通过对抛出的异常类型的验证。

$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

概括一下您的

  • 由于从给定实例开始处理对象类型在 PowerShell 中并不常见,实例的类型可能是 non-public 通常不明显,只要类型 派生自 (是子类)预期的 public类型。

    • 虽然您可以通过 .GetType() 获取对象的非 public 类型 ,但您不能通过 类型文字引用它(例如 [System.Management.Automation.ParameterBindingException]),例如用于 Pester 测试或参数声明。
  • 您可以在任何给定实例上调用 .GetType().IsPublic 来检查其类型是否为 public,并调用 .GetType().BaseType 来获取该类型的基类型 - 尽管您可能必须调用后者 多个 类型,直到达到 .IsPublic$true 的类型 - 请参阅底部的便利功能。

在手头的案例中 .GetType().BaseType.FullName 足以达到 public 基本类型:

# 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

以上结果:

System.Management.Automation.ParameterBindingValidationException  # non-public
System.Management.Automation.ParameterBindingException            # public base type

下面是便利函数Get-PublicType,给定任何实例,它报告实例类型的继承链中最派生的类型public(可能是实例的类型本身:

调用示例[​​=66=]:

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源代码:

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
    }

  }

}