如何使用 Pester 模拟模块不可用的 cmdlet?

How can I use Pester to mock a cmdlet in which the module is not available?

我正在尝试为我的 Azure 自动化运行手册编写 Pester 测试。 Runbook 脚本使用 Get-AutomationVariable cmdlet,我试图通过以下方式模拟它:

mock 'Get-AutomationVariable' {return "localhost:44300"} -ParameterFilter { $name -eq "host"}

导致错误

CommandNotFoundException: The term 'Get-AutomationVariable' is not recognized as the name of a cmdlet, function, script file, or operable program.

Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

-ModuleName 参数的使用似乎不合适,因为我是从脚本而不是模块调用方法。尝试提供存根模块会导致抛出相同的错误。

. "$here$sut" "CN=teset, OU=Test" "CN=SubCA02, OU=Test"

Get-Module -Name RunbookMock | Remove-Module
New-Module -Name RunbookMock -ScriptBlock {
  function Get-AutomationVariable {
    [CmdLetBinding()]
    param(
      [string]$Name
    )

    ""
  }
  Export-ModuleMember Get-AutomationVariable
} | Import-Module -Force

describe 'Pin-Certificate' {
  it 'should add an entry to the pinned certificate list'{
    mock 'Get-AutomationVariable' { return "sastoken"} -ParameterFilter {  $Name -eq "StorageSasToken"} -
    mock 'Get-AutomationVariable' {return "localhost:44300"} -ParameterFilter { $name -eq "host"}
  }
}

核心问题是语句的顺序。被测脚本是在模拟声明之前获取的。重新排序声明模拟然后获取脚本的语句解决了这个问题,@MarkWragg 的优秀建议有助于进一步简化代码。其最终工作状态如下

$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.'

  function Get-AutomationVariable {
    [CmdLetBinding()]
    param([string]$Name)
  }

describe 'Pin-Certificate' {
  it 'should add an entry to the pinned certificate list' {
    mock 'Get-AutomationVariable' -MockWith { "sastoken" } -ParameterFilter {  $Name -eq "StorageSasToken"}
    mock 'Get-AutomationVariable' -MockWith { "localhost:44300" } -ParameterFilter { $Name -eq "host"}
    mock 'Invoke-WebRequest' -MockWith { @{ "StatusCode" = "204"; }   }

    # dot source the sut, invoking the default function
    $result = (. "$here$sut" "Subject" "Issuer" "Thumbprint")

    $result.Issuer | Should be "Issuer"
    $result.Subject | Should Be "Subject"
    $result.Thumbprint | Should Be "Thumbprint"
  }

根据评论,您的代码应该可以工作。过去我只是声明了一个空函数而不是一个完整的模块。例如:

function MyScript {
    Get-FakeFunction
}

Describe 'Some Tests' {

    function Get-Fakefunction {}

    Mock 'Get-Fakefunction' { write-output 'someresult' }

    $Result = MyScript

    it 'should invoke Get-FakeFunction'{
        Assert-MockCalled 'Get-Fakefunction' -Times 1              
    }
    it 'should return someresult'{
        $Result | Should -Be 'someresult'
    }
}