【问题标题】:String parameter is not resolved when parameterizing Mock ParameterFilter in Pester在 Pester 中参数化 Mock ParameterFilter 时未解析字符串参数
【发布时间】:2021-09-17 01:42:28
【问题描述】:

我正在使用 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}
    }

在这个辅助方法中,我模拟了依赖Get-VstsInput,它有一个参数$Name

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

    MockVstsInput "targetapi" "api-name"

这意味着如果 Get-VstsInput 使用 $Name 参数“targetapi”调用,那么它应该返回“api-name”。通常这种参数化在其他语言中也有效(例如: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 字符串在我的脚本块中没有解析,所以模拟不会发生。

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

  • 在谓词脚本块{ $inputName -eq $Name}中交换相等比较的成员

  • 在脚本块中使用$ExecutionContext.InvokeCommand.ExpandString($inputName) 来解析$inputName 字符串

  • 使用[ScriptBlock]::create($Name -eq $inputName) 创建一个脚本块,然后在-ParameterFilter 中使用它

  • 最后但同样重要的是,我尝试调用我的脚本块的GetNewClosure,但它也没有帮助:{ $Name -eq $inputName}.GetNewClosure()

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

【问题讨论】:

标签: powershell pester


【解决方案1】:

你很接近。关键在于您如何构建所需的脚本块。

对于 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,您将在每次影响任何先前定义的 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 个参数 scriptblocks 和稍后解析的变量,这将不再在范围内,解决方案是使用硬编码的变量值创建 scriptblocks。


另外,请参阅 this answer 了解使用 BeforeEach { } 的方法

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-30
    • 1970-01-01
    • 1970-01-01
    • 2012-11-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多