我如何用 Pester 模拟 $host.ui.PromptForChoice
How do I mock $host.ui.PromptForChoice with Pester
考虑到下面的 Powershell 代码,有没有办法在没有 internalMenuWrapper
函数的情况下模拟 $host.ui.PromptForChoice
?
<#
.Synopsis
wrap the menu so we can mock calls to it
#>
function internalMenuWrapper {
param (
[Parameter(Mandatory=$true)]
$prompt,
[Parameter(Mandatory=$true)]
$options
)
return = $host.ui.PromptForChoice("Waiting for user input", $prompt, [System.Management.Automation.Host.ChoiceDescription[]]$options, 0)
}
<#
.Synopsis
Create a menu with an array of choices and return the result
#>
function Invoke-Menu($prompt, $opts) {
$options = @()
foreach ($opt in $opts) {
$options += $(new-object System.Management.Automation.Host.ChoiceDescription $opt)
}
$index = internalMenuWrapper $prompt $options
$opts[$index]
}
Describe 'Invoke-Menu' {
Context "when called" {
It "returns the object that was selected" {
#mock fails
Mock internalMenuWrapper { return 0 }
$result = Invoke-Menu "test menu" @("pass", "fail")
$result | Should -Be "pass"
}
}
}
正如 Mike Shepard 在评论中指出的那样,Pester 不支持 mocking methods,只有 commands 可以模拟(cmdlet、函数、别名、外部程序).
您可以 通过使用 Get-Host
cmdlet 而不是 $host
和 mock that 来解决这个问题:
function Invoke-Menu($prompt, $choices) {
$choiceObjects = [System.Management.Automation.Host.ChoiceDescription[]] $choices
# Use Get-Host instead of $host here; $host cannot be mocked, but Get-Host can.
$index = (Get-Host).ui.PromptForChoice("Waiting for user input", $prompt, $choiceObjects, 0)
$choices[$index]
}
Describe 'Invoke-Menu' {
Context "when called" {
It "returns the object that was selected" {
# Mock Get-Host with a dummy .ui.PromptForChoice() method that instantly
# returns 0 (the first choice).
Mock Get-Host {
[pscustomobject] @{
ui = Add-Member -PassThru -Name PromptForChoice -InputObject ([pscustomobject] @{}) -Type ScriptMethod -Value { return 0 }
}
}
Invoke-Menu 'test menu' '&pass', '&fail' | Should -Be '&pass'
}
}
}
正如您指出的那样 on GitHub,如果建议的 Read-Choice
cmdlet 曾经被实现(作为 $host.ui.PromptForChoice()
的 PS 友好包装器),它可能会被嘲笑直接(并且没有要测试的自定义代码)。
考虑到下面的 Powershell 代码,有没有办法在没有 internalMenuWrapper
函数的情况下模拟 $host.ui.PromptForChoice
?
<#
.Synopsis
wrap the menu so we can mock calls to it
#>
function internalMenuWrapper {
param (
[Parameter(Mandatory=$true)]
$prompt,
[Parameter(Mandatory=$true)]
$options
)
return = $host.ui.PromptForChoice("Waiting for user input", $prompt, [System.Management.Automation.Host.ChoiceDescription[]]$options, 0)
}
<#
.Synopsis
Create a menu with an array of choices and return the result
#>
function Invoke-Menu($prompt, $opts) {
$options = @()
foreach ($opt in $opts) {
$options += $(new-object System.Management.Automation.Host.ChoiceDescription $opt)
}
$index = internalMenuWrapper $prompt $options
$opts[$index]
}
Describe 'Invoke-Menu' {
Context "when called" {
It "returns the object that was selected" {
#mock fails
Mock internalMenuWrapper { return 0 }
$result = Invoke-Menu "test menu" @("pass", "fail")
$result | Should -Be "pass"
}
}
}
正如 Mike Shepard 在评论中指出的那样,Pester 不支持 mocking methods,只有 commands 可以模拟(cmdlet、函数、别名、外部程序).
您可以 通过使用 Get-Host
cmdlet 而不是 $host
和 mock that 来解决这个问题:
function Invoke-Menu($prompt, $choices) {
$choiceObjects = [System.Management.Automation.Host.ChoiceDescription[]] $choices
# Use Get-Host instead of $host here; $host cannot be mocked, but Get-Host can.
$index = (Get-Host).ui.PromptForChoice("Waiting for user input", $prompt, $choiceObjects, 0)
$choices[$index]
}
Describe 'Invoke-Menu' {
Context "when called" {
It "returns the object that was selected" {
# Mock Get-Host with a dummy .ui.PromptForChoice() method that instantly
# returns 0 (the first choice).
Mock Get-Host {
[pscustomobject] @{
ui = Add-Member -PassThru -Name PromptForChoice -InputObject ([pscustomobject] @{}) -Type ScriptMethod -Value { return 0 }
}
}
Invoke-Menu 'test menu' '&pass', '&fail' | Should -Be '&pass'
}
}
}
正如您指出的那样 on GitHub,如果建议的 Read-Choice
cmdlet 曾经被实现(作为 $host.ui.PromptForChoice()
的 PS 友好包装器),它可能会被嘲笑直接(并且没有要测试的自定义代码)。