I have a PowerShell .ps1 file which contains functions at the top of the script followed by different commands calling these functions. I am using Pester to unit test my script file.
How do I mock a function that is within my PowerShell .ps1 script?
I have tried mocking the function, but I get an error saying "could not find command".
I have also tried adding an empty "dummy" function in the describe block. This doesn't give me the above error, but it is not mocking the function within the script correctly.
I have two files. One to hold the tests and another that holds the functions and calls to the functions. Below are two examples:
Function Backup-Directory([switch]$IsError)
{
If($IsError)
{
Write-Error "Error"
Exit 1
}
}
Backup-Directory $true
$here = (Split-Path -Parent $MyInvocation.MyCommand.Path) -replace '\\test', '\main'
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.'
$productionFile = "$here\$sut"
Describe "File1" {
Context "When the Back-Directory outputs an error." {
# Arrange
Mock Back-Directory { }
Mock Write-Error
# Act
& $productionFile
$hasSucceeded = $?
# Assert
It "Calls Backup-Directory" {
Assert-MockCalled Backup-Directory -Exactly 1 -ParameterFilter {
$IsError -eq $true
}
}
It "Writes error message." {
Assert-MockCalled Write-Error -Exactly 1 -ParameterFilter {
$Message -eq "Error"
}
}
It "Exits with an error." {
$hasSucceeded | Should be $false
}
}
}
I don't think this is possible. At least with your current implementation. I asked this same question a while back... Pester Issue 414
BUT you could split out that inner function into another script file in the same directory allowing you to unit test and mock it. You would just have to dot source the function in your main script file to be able to use it:
Main-Function.ps1:
# Main script
function Main-Function {
# if debugging, set moduleRoot to current directory
if ($MyInvocation.MyCommand.Path) {
$moduleRoot = Split-Path -Path $MyInvocation.MyCommand.Path
}else {
$moduleRoot = $PWD.Path
}
# dot source the inner function
. "$moduleRoot\Inner-Function.ps1"
Write-Output "This is the main script. Calling the inner function"
Inner-Function
Write-Output "Back in the main script"
}
Inner-Function.ps1:
function Inner-Function {
Write-Output "This is the inner function"
}
Main-Function.Tests.ps1:
$moduleRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
# Load Testing Function
. "$moduleRoot\Main-Function.ps1"
# Load Supporting Functions
. "$moduleRoot\Inner-Function.ps1"
Describe 'Main Script' {
Mock Inner-Function { return "This is a mocked function" }
It 'uses the mocked function' {
(Main-Function -match "This is a mocked function") | Should Be $true
}
}
This is a really nice approach because we can unit test the inner function and as the logic grows, adding tests to it is very easy (and can be done in isolation from the rest of the scripts/functions).
Inner-Functions.Tests.ps1:
$moduleRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
# Load Testing Function
. "$moduleRoot\Inner-Function.ps1"
Describe 'Inner Function' {
It 'outputs some text' {
Inner-Function | Should Be "This is the inner function"
}
}
There are two main points here...
Finding the dependent function location regardless of your current execution directory...
if ($MyInvocation.MyCommand.Path) {
$moduleRoot = Split-Path -Path $MyInvocation.MyCommand.Path
}else {
$moduleRoot = $PWD.Path
}
AND
Dot Sourcing depending functions in the main function as well as the unit test files... . "$moduleRoot\Inner-Function.ps1"
Split-Path -Path $MyInvocation.MyCommand.Path
is $null
in a user session, but will NOT be $null
when invoked from within an execution context. Meaning, you could be in C:\users\nhudacin
and still load this script/module correctly. Otherwise you would have to always execute this script/module from within the same directory as where it's located without using $MyInvoation
variable.