在 Pester 中参数化 Mock ParameterFilter 时未解析字符串参数

String parameter is not resolved when parameterizing Mock ParameterFilter in Pester

我正在使用 Pester 测试库(5.0.2 版)来测试我的 PowerShell 脚本(5.1 版)并模拟其依赖项。

Pester 有一个 Mock 方法可以用来模拟依赖关系。更多信息 here.

我正在尝试创建一个包装此 Mock 方法的辅助方法,以使我的代码更具可读性:

    Function MockVstsInput {
        Param(
            [Parameter(Mandatory=$true, Position=1)]
            [string]$inputName,
            [Parameter(Mandatory=$true, Position=2)]
            [string]$returnValue
        )

        Mock Get-VstsInput {return $returnValue} -ParameterFilter { $Name -eq $inputName}
    }

在这个辅助方法中,我正在模拟具有参数 $Name

的依赖项 Get-VstsInput

然后我在我的测试脚本中使用这段代码:

    MockVstsInput "targetapi" "api-name"

意思是如果 Get-VstsInput 是用 $Name 参数“targetapi”调用的,那么它应该 return “api-name”。通常这种参数化适用于其他语言(f.e.: C# 或 Java),但这里 $inputName 字符串未在 MockVstsInput 方法中解析。

当我在生产代码中调用 Get-VstsInput 方法时:

    $newapi=Get-VstsInput -Name targetapi

然后在日志中我有以下 Mock 信息:

Mock: Running mock filter {  $Name -eq $inputName } with context: Name = targetapi. 
Mock: Mock filter did not pass. 

我们可以看到 $inputName 字符串在我的脚本块中没有解析,所以没有发生模拟。

到目前为止我已经尝试过但没有成功:

您认为我的问题的根本原因是什么?预先感谢所有帮助!

你很接近。这完全取决于您如何构建所需的脚本块。

对于 ParameterFilter 脚本块,您需要使用反引号对 $Name 进行转义,以便将其创建为变量。 $inputName 将替换为我们的变量值,因此您需要用引号引起来。

# $inputName = 'Daniel'
$sb_param = [scriptblock]::Create("`$Name -eq '$inputName'")

这样脚本块中的最终语句看起来像

{ $Name -eq 'Daniel' }

同样,在我们的 MockWith 块中,我们也需要用引号括起来,否则脚本块不会包含字符串,而是无效的命令

# $returnValue = 'Hi Daniel'
$sb_return = [scriptblock]::Create("'$returnValue'")

变成

{ 'Hi Daniel' }

如果我们在创建脚本块时不包含引号,脚本块将包含语句 Hi Daniel,它将因未找到命令而失败


这是一个工作示例中的所有内容

Describe 'Setting up a Mock from a Function' {
    BeforeAll {
        Function Get-VstsInput {
            [CmdletBinding()]
            Param(
                $Name
            )
            'Not Mocked'
        }

        Function MockVstsInput {
            Param(
                [Parameter(Mandatory = $true, Position = 1)]
                [string]$inputName,
                [Parameter(Mandatory = $true, Position = 2)]
                [string]$returnValue
            )
            $sb_param = [scriptblock]::Create("`$Name -eq '$inputName'")
            $sb_return = [scriptblock]::Create("'$returnValue'")
            Mock Get-VstsInput -MockWith $sb_return -ParameterFilter $sb_param
        }
        MockVstsInput -inputname 'Daniel' -returnValue 'Hi Daniel'
    }

    It 'Should mock' {
        $test = Get-VstsInput -Name 'Daniel'
        $test | Should -Be 'Hi Daniel'
    }
}


澄清一下,这是一个范围界定问题。发生的事情是您稍后在不同的范围内将这些脚本块提供给 运行 的模拟命令。当您 运行 模拟命令时,变量 $inputName$returnValue 在调用时未在这些脚本块中定义。这些变量仅在 MockVstsInput 函数中可用,并在函数完成后被清除。

为了说明这一点,下面的代码可以工作,但我不建议这样做,因为如果你 运行 函数不止一次定义不同的模拟,你将覆盖全局变量每次影响任何先前定义的 Mocks

        Function MockVstsInput {
            Param(
                [Parameter(Mandatory = $true, Position = 1)]
                [string]$inputName,
                [Parameter(Mandatory = $true, Position = 2)]
                [string]$returnValue
            )           
            $global:mockInputName = $inputName
            $global:mockReturnValue = $returnValue
            Mock Get-VstsInput -ParameterFilter {$Name -eq $global:mockInputName} -MockWith {$global:mockReturnValue}
        }

因此,与其为 2 个参数脚本块提供稍后将不再在范围内解析的变量,不如使用硬编码的变量值创建脚本块。


此外,请参阅 this answer 了解使用 BeforeEach { }

的方法