Reputation: 19748
I have a PowerShell function I want to test with Pester:
function Install-RequiredModule (
[string]$ModuleName,
[string]$RepositoryName,
[string]$ProxyUrl
)
{
# Errors from Install-Module are non-terminating. They won't be caught using
# try - catch. So check $Error instead.
# Clear errors so we know if one shows up it must have been due to Install-Module.
$Error.Clear()
# Want to fail silently, without displaying anything in console to scare the user,
# because it's valid for Install-Module to fail for a user behind a proxy server.
Install-Module -Name $ModuleName -Repository $RepositoryName `
-ErrorAction SilentlyContinue -WarningAction SilentlyContinue
if ($Error.Count -eq 0)
{
# throw 'NO error'
return
}
# There was an error so try again, this time with proxy details.
$proxyCredential = Get-Credential -Message 'Please enter credentials for proxy server'
# No need to Silently Continue this time. We want to see the error details.
$Error.Clear()
Install-Module -Name $ModuleName -Repository $RepositoryName `
-Proxy $ProxyUrl -ProxyCredential $proxyCredential
if ($Error.Count -gt 0)
{
throw $Error[0]
}
if (-not (Get-InstalledModule -Name $ModuleName -ErrorAction SilentlyContinue))
{
throw "Unknown error installing module '$ModuleName' from repository '$RepositoryName'."
}
Write-Output "Module '$ModuleName' successfully installed from repository '$RepositoryName'."
}
This function can call Install-Module twice. It first tries without proxy credentials, as if it has direct access to the internet. If that fails it tries again, this time with proxy credentials.
How can I test this functionality with Pester?
I read in the PowerShell forums, here, that I should be a able to mock the same command twice with different parameter filters. So this is what I tried:
function ExecuteInstallRequiredModule ()
{
Install-RequiredModule -ModuleName 'TestModule' -RepositoryName 'TestRepo' `
-ProxyUrl 'http://myproxy'
}
Describe 'Install-RequiredModule' {
$securePassword = "mypassword" |
ConvertTo-SecureString -asPlainText -Force
$psCredential = New-Object System.Management.Automation.PSCredential ('MyUserName', $securePassword)
Mock Get-Credential { return $psCredential }
# Want to add an error to $Error without it being written to the host.
Mock Install-Module { Write-Error "Some error" -ErrorAction SilentlyContinue } `
-ParameterFilter { $Name -eq 'TestModule' -and $Repository -eq 'TestRepo' -and $ErrorAction -eq 'SilentlyContinue' -and $WarningAction -eq 'SilentlyContinue'}
Mock Install-Module { return $Null } `
-ParameterFilter { $Name -eq 'TestModule' -and $Repository -eq 'TestRepo' -and $Proxy -eq 'http://myproxy' -and $ProxyCredential -eq $psCredential }
Mock Get-InstalledModule { return @('Non-null text') }
It 'attempts to install module a second time if first attempt fails' {
ExecuteInstallRequiredModule
#Assert-VerifiableMock
#Assert-MockCalled Install-Module -Scope It -Times 2
}
}
Uncommenting the line in the function under test that says # throw 'NO error'
I find that the $Error.Count is 0 after the first call to Install-Module. So the mock that is creating a non-terminating error is not being called and the function returns before the second call to Install-Module.
Upvotes: 1
Views: 1592
Reputation: 8442
The problem seems to be that Pester blocks filtering on common parameters, so your use of 'ErrorAction', etc is causing your filter to fail.
You can see the parameters being removed from mocked functions at around line 254 in the Pester mock code: Mock.ps1
And also, testing for this removal is one of Pester's own unit tests (line 283): Mock.tests.ps1
Upvotes: 2
Reputation: 19748
For anyone in a similar situation, here's the final version of the test that worked:
function Install-RequiredModule (
[string]$ModuleName,
[string]$RepositoryName,
[string]$ProxyUrl
)
{
try
{
Install-Module -Name $ModuleName -Repository $RepositoryName `
-ErrorAction Stop
return
}
catch {}
# There was an error so try again, this time with proxy details.
$proxyCredential = Get-Credential -Message 'Please enter credentials for proxy server'
# No need to Silently Continue this time. We want to see the error details.
$Error.Clear()
Install-Module -Name $ModuleName -Repository $RepositoryName `
-Proxy $ProxyUrl -ProxyCredential $proxyCredential
if ($Error.Count -gt 0)
{
throw $Error[0]
}
if (-not (Get-InstalledModule -Name $ModuleName -ErrorAction SilentlyContinue))
{
throw "Unknown error installing module '$ModuleName' from repository '$RepositoryName'."
}
Write-Output "Module '$ModuleName' successfully installed from repository '$RepositoryName'."
}
#region Tests *************************************************************************************
function ExecuteInstallRequiredModule ()
{
Install-RequiredModule -ModuleName 'TestModule' -RepositoryName 'TestRepo' `
-ProxyUrl 'http://myproxy'
}
Describe 'Install-RequiredModule' {
$securePassword = "mypassword" |
ConvertTo-SecureString -asPlainText -Force
$psCredential = New-Object System.Management.Automation.PSCredential ('MyUserName', $securePassword)
Mock Get-Credential { return $psCredential }
Mock Install-Module { Write-Error "Some error" }
Mock Install-Module { return $Null } -ParameterFilter { $Proxy -ne $Null -and $ProxyCredential -ne $Null }
Mock Get-InstalledModule { return @('Non-null text') }
It 'attempts to install module a second time, with proxy, if first attempt fails' {
ExecuteInstallRequiredModule
Assert-MockCalled Install-Module -Scope It -Times 2 -Exactly
}
}
#endregion
Upvotes: 0
Reputation: 2676
You can call the install-module
command with -ErrorAction Stop
inside a try catch loop.
try
{
#run with no credentials
Install-Module -Name $ModuleName -Repository $RepositoryName -ErrorAction stop -WarningAction SilentlyContinue
}
catch
{
#when fails, run with proxy credentials
Install-Module -Name $ModuleName -Repository $RepositoryName -Proxy $ProxyUrl -ProxyCredential $proxyCredential
}
You can have multiple catch{}
block for the same try command that would catch different types of failures and execute a script block of your choice.
Upvotes: 1